2012-03-16 9 views
6

Teniendo en cuenta el código:Herencia múltiple: tamaño de la clase para punteros virtuales?

class A{}; 

class B : public virtual A{}; 

class C : public virtual A{}; 

class D : public B,public C{}; 

int main(){ 
cout<<"sizeof(D)"<<sizeof(D); 
return 0; 
} 

Salida: sizeof (D) 8

Cada clase contiene su propio puntero virtual sólo no de cualquiera de su clase base, Así que, ¿por qué el tamaño de la clase (D) es 8?

+0

Debe indicar en qué compilador y arquitectura se encuentra. –

+0

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. –

Respuesta

3

Depende de la implementación del compilador. Mi compilador es Visual C++ 2005. Stdio

código como este:

int main(){ 
    cout<<"sizeof(B):"<<sizeof(B) << endl; 
    cout<<"sizeof(C):"<<sizeof(C) << endl; 
    cout<<"sizeof(D):"<<sizeof(D) << endl; 
    return 0; 
} 

Es sólo una es la salida

sizeof(B):4 
sizeof(C):4 
sizeof(D):8 

clase B tiene puntero virtual. Entonces sizeof(B)=4. Y la clase C también lo es.

Pero la herencia múltiple D class B y class C. La compilación no fusiona las dos tablas virtuales. Por lo tanto, class D tiene dos punteros virtuales para cada tabla virtual.

Si D solo hereda una clase y no hereda virtual. Combinará la mesa virtual.

+1

¡Estoy de acuerdo! Estamos adivinando porque es un detalle de implementación del compilador (con su versión/arquitectura y diferencias de plataforma) pero es plausible. –

2

Depende de la implementación del compilador, por lo que debe especificar qué compilador está utilizando. De todos modos D deriva de dos clases por lo que contiene punteros a B y C vtables indicadores de la clase de base (no sé un buen nombre para esto).

Para probar esto, puede declarar un puntero a B y un puntero a C y echó la dirección de D al puntero de clase base. ¡Vuelca esos valores y verás que son diferentes!

EDITAR
prueba hecha con Visual C++ 10.0, 32 bits.

class Base 
{ 
}; 

class Derived1 : public virtual Base 
{ 
}; 

class Derived2 : public virtual Base 
{ 
}; 

class Derived3 : public virtual Base 
{ 
}; 

class ReallyDerived1 : public Derived1, public Derived2, public Derived3 
{ 
}; 

class ReallyDerived2 : public Derived1, public Derived2 
{ 
}; 

class ReallyDerived3 : public Derived2 
{ 
}; 

void _tmain(int argc, _TCHAR* argv[]) 
{ 
std::cout << "Base: " << sizeof(Base) << std::endl; 
std::cout << "Derived1: " << sizeof(Derived1) << std::endl; 
std::cout << "ReallyDerived1: " << sizeof(ReallyDerived1) << std::endl; 
std::cout << "ReallyDerived2: " << sizeof(ReallyDerived2) << std::endl; 
std::cout << "ReallyDerived3: " << sizeof(ReallyDerived3) << std::endl; 
} 

salida, conjetura, no es sorprendente:

  • Base: 1 byte (OK, esto es una sorpresa, al menos para mí).
  • Derived1: 4 bytes
  • ReallyDerived1: 12 bytes (4 bytes por clase de base debido a la herencia múltiple)
  • ReallyDerived2: 8 bytes (como supuso)
  • ReallyDerived3: 4 bytes (sólo una clase base con Virtual herencia en la ruta, pero esto no es virtual).

Añadiendo un método virtual a la base obtienes 4 bytes más para cada clase. Por lo tanto, probablemente los bytes adicionales no sean punteros vtable, sino punteros de clase base utilizados en la herencia múltiple, este comportamiento no cambia la eliminación de la herencia virtual (pero si no es virtual, el tamaño no cambia al agregar más bases).

+1

En este caso, me sorprendería si hubiera algún apuntador a 'vtables'. Necesitará al menos 3. Excepto que sin ninguna función virtual, no requiere ninguna. (Y no hay forma de convertir un 'A *' a un 'B *'.) –

+0

I ** supongo ** (si alguien sabe por favor corrígeme!) El compilador crea un puntero a la clase base incluso sin métodos virtuales porque herencia múltiple (porque es necesario para downcasts). Solo debe haber DOS punteros a la clase base vtable, uno para la clase base y nada para ** C ** en sí mismo porque no hay métodos virtuales.
Quise decir: declarar un puntero B * pB y otro puntero C * pC. Luego baje un puntero a D y asígnelo a pB y pC. :) –

+0

Estoy usando el compilador de g ++ – Luv

1

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.

+0

Consulte con más de 2 clases base, creo que no se debe a métodos virtuales sino a puntero a la clase base "vtable" (presente incluso si no existe) –

+0

Si no hay funciones virtuales presentes, un 'vtable' no es necesario, pero algunos tipos de punteros a la clase base virtual son (ya que son necesarios para convertir un 'Derived *' en un 'VB *'). –

0

Usted está haciendo demasiadas suposiciones. Esto depende en gran medida del ABI, por lo que debe consultar la documentación de su plataforma (supongo que se está ejecutando en una plataforma de 32 bits).

Lo primero es que no hay funciones virtuales en su ejemplo, y eso significa que ninguno de los tipos contiene realmente un puntero a la tabla virtual. Entonces, ¿de dónde vinieron esos 2 punteros? (Supongo que estás en una arquitectura de 32 bits). Bueno, la herencia virtual es la respuesta. Cuando hereda virtualmente, la ubicación relativa de la base virtual (A) con respecto a los elementos adicionales en el tipo derivado (B, C) cambiará a lo largo de la cadena de herencia. En el caso de un objeto B o C, el compilador puede establecer los tipos como [A, B '] y [A, C'] (donde X 'es los campos adicionales de X no presentes en A).

Ahora herencia virtual significa que solo habrá un subobjeto A en el caso de D, por lo que el compilador puede diseñar el tipo D como [A, B ', C', D] o [A, C ', B ', D] (o cualquier otra combinación, A podría estar al final del objeto, etc., esto se define en el ABI). Entonces, ¿qué implica esto? Esto implica que las funciones miembro de B y C no pueden asumir dónde podría estar el subobjeto A (en el caso de herencia no virtual, la ubicación relativa es conocida), porque el tipo completo podría ser en realidad otro tipo abajo de la cadena.

La solución al problema es que tanto B como C generalmente contienen un puntero puntero a base extra, similar pero no equivalente al puntero virtual. De la misma manera que el vptr se utiliza para enviar dinámicamente a una función, este puntero adicional se usa para encontrar dinámicamente la base.

Si está interesado en todos estos detalles, le recomiendo que lea el Itanium ABI, que es ampliamente utilizado no solo en Itanium sino también en otras arquitecturas Intel 64 (y una versión modificada en 32 arquitecturas) por diferentes compiladores.

Cuestiones relacionadas