Primero: sin funciones virtuales, es probable que no haya un vptr
en absoluto en las clases. Los 8 bytes que está viendo son un artefacto de la forma en que se implementa la herencia virtual.
A menudo es posible que varias clases en una jerarquía compartan el mismo vptr
. Para que esto ocurra, es necesario que su compensación en la clase final sea la misma, y para la lista de entradas vtable en la clase base sea una secuencia inicial la lista de entradas vtable en la clase derivada .
Ambas condiciones se cumplen en casi todas las implementaciones para herencia simple .No importa cuán profunda sea la herencia, generalmente habrá solo uno vptr
, compartido entre todas las clases.
En el caso de la herencia múltiple, siempre habrá al menos un clase para la cual no se cumplen estos requisitos, ya que la base de dos clases no pueden tener una dirección de inicio común, y menos que tengan exactamente las mismas funciones virtuales, solo una vtable podría ser una secuencia inicial de la otra.
La herencia virtual agrega otra peculiaridad, ya que la posición de la base virtual relativa a la clase que hereda de ella variará dependiendo del resto de la jerarquía. La mayoría de las implementaciones que he visto usan un puntero separado para esto, aunque también debería ser posible poner esta información en el vtable.
Si tomamos la jerarquía, la adición de funciones virtuales de manera que estamos seguro de tener un vptr
, nos damos cuenta de que B
y D
todavía se puede compartir un vtable
, pero ambos A
y C
necesita separar vtables
. Esto significa que si sus clases tienen funciones virtuales, necesitaría al menos tres vptr
. (A partir de este llego a la conclusión de que su aplicación está utilizando punteros separados a la base virtual. Con B
y D
compartiendo el mismo puntero y C
con su propio puntero. Y, por supuesto, no lo hace A
tienen una base virtual y no necesita un puntero a sí mismo.)
Si usted está tratando de analizar exactamente lo que está pasando, me gustaría sugerir la adición de una nueva función virtual en cada clase, y la adición de un puntero de tamaño tipo integral que Inicializa con un valor conocido diferente para cada clase . (Utilice constructores para establecer el valor.) Luego cree una instancia de la clase, tómela, luego genere la dirección para cada clase base . Y luego volcar la clase: los valores fijos conocidos ayudarán en identificando dónde se encuentran los diferentes elementos. Algo así como:
struct VB
{
int vb;
VB() : vb(1) {}
virtual ~VB() {}
virtual void fvb() {}
};
struct Left : virtual VB
{
int left;
Left() : left(2) {}
virtual ~Left() {}
virtual void fvb() {}
virtual void fleft() {}
};
struct Right : virtual VB
{
int right;
Right() : right(3) {}
virtual ~Right() {}
virtual void fvb() {}
virtual void fright() {}
};
struct Derived : Left, Right
{
int derived;
Derived() : derived(5) {}
virtual ~Derived() {}
virtual void fvb() {}
virtual void fleft() {}
virtual void fright() {}
virtual void fderived() {}
};
Es posible que desee añadir un Derived2
, que deriva de Derived
y ver lo que ocurre con las direcciones relativas entre, por ejemplo, Left
y VB
según si el objeto tiene el tipo Derived
o Derived2
.
Debe indicar en qué compilador y arquitectura se encuentra. –
Nada en el estándar habla sobre punteros virtuales que no existen; realmente existen. Entonces el tamaño es 8 porque el compilador necesita que sea 8. Es un detalle de implementación y no tiene mucho sentido especular sobre él, ya que puede ser diferente en otro compilador. –