2008-12-28 13 views
12

El siguiente código imprime 20, es decir, sizeof (z) es 20.Pregunta sobre la herencia múltiple, clases base virtuales, y el tamaño del objeto en C++

#include <iostream.h> 
class Base 
{ 
     public: 
      int a; 
}; 

class X:virtual public Base 
{ 
     public: 
      int x; 
}; 

class Y:virtual public Base 
{ 
     public: 
      int y; 
}; 

class Z:public X,public Y 
{ 
}; 

int main() 
{ 
Z z; 
cout << sizeof(z) <<endl; 
} 

Mientras que si no uso clases base virtuales aquí, es decir, para el siguiente código: sizeof (z) es 16.

#include <iostream.h> 
class Base 
{ 
     public: 
      int a; 
}; 

class X:public Base 
{ 
     public: 
      int x; 
}; 

class Y:public Base 
{ 
     public: 
      int y; 
}; 

class Z:public X,public Y 
{ 
}; 

int main() 
{ 
Z z; 
cout << sizeof(z) <<endl; 
} 

¿por qué es sizeof (z) más (20) en el primer caso? ¿No debería ser 12, ya que Base se incluirá solo una vez en Z?

Respuesta

20

Veamos el diseño de clase de los dos casos.

Sin lo virtual, tiene dos clases base ("X" y "Y") con un entero cada una, y cada una de esas clases ha integrado en ellas una clase base "Base" que también tiene un número entero. Eso es 4 enteros, 32 bits cada uno, totalizando sus 16 bytes.

Offset Size Type Scope Name 
    0  4 int Base  a 
    4  4 int  X  x 
    8  4 int Base  a 
    12  4 int  Y  y 
    16 size (Z members would come at the end) 

(Editar:. He escrito un programa en DJGPP para obtener el diseño y ajustado la mesa para dar cuenta de ello)

Ahora vamos a hablar de las clases base virtuales: los que sustituyen la instancia actual de la clase con un puntero a una instancia compartida. Su clase "Z" tiene solo una clase "Base", y ambas instancias de "X" e "Y" apuntan a ella. Por lo tanto, tiene números enteros en X, Y y Z, pero solo tiene la Z. Eso significa que tiene tres enteros o 12 bytes. Pero X e Y también tienen un puntero a la Z compartida (de lo contrario, no sabrían dónde encontrarla). En una máquina de 32 bits, dos punteros agregarán 8 bytes adicionales. Esto suma los 20 que ves. El diseño de memoria podría ser algo como esto (no he verificado que ... El brazo tiene un ejemplo donde el orden es X, Y, Z, entonces Base):

Offset Size  Type Scope Name Value (sort of) 
    0  4 Base offset  X  ? 16 (or ptr to vtable) 
    4  4   int  X  x 
    8  4 Base offset  Y  ? 16 (or ptr to vtable) 
    12  4   int  Y  y 
    16  4   int Base  a 
    20 size (Z members would come before the Base) 

Así que la diferencia de memoria es una combinación de dos cosas: un entero menos y dos punteros más. Al contrario de otra respuesta, no creo que los vtables paguen ninguna (edición) directa (/ edición) en esto, ya que no hay funciones virtuales.

Editar: ppinsider ha proporcionado más información sobre el caso gcc, en el que demuestra que gcc implementa el puntero a la clase base virtual haciendo uso de un vtable vacío (es decir, sin funciones virtuales). De esa forma, si hubiera funciones virtuales, no requeriría un puntero adicional en la instancia de la clase, lo que requeriría más memoria. Sospecho que la desventaja es una indirección adicional para llegar a la clase base.

Podríamos esperar que todos los compiladores hicieran esto, pero quizás no. La ARM página 225 discute clases base virtuales sin mencionar vtables. La página 235 aborda específicamente "clases base virtuales con funciones virtuales" y tiene un diagrama que indica un diseño de memoria donde hay punteros de las partes X e Y que están separadas de los punteros a la tabla virtual. Aconsejaría a cualquiera que no dé por sentado que el puntero a Base se implementará en términos de una tabla.

+0

Mark, ¿cómo generó ese diseño? –

+0

Si se refiere al formato de tabla, es solo código. Solo adiviné el contenido. Editaré mi respuesta con más detalles y una referencia a la respuesta de ppinsider. – markets

+1

disfruté leyéndolo. gracias +1 –

9

La respuesta de Mark Santesson se basa en el dinero, pero la afirmación de que no hay tablas virtuales es incorrecta. Puede usar g ++ -fdump-class-hierarchy para mostrar lo que está sucediendo.Aquí está el caso de no virtuales:

Class Base 
    size=4 align=4 
    base size=4 base align=4 
Base (0x19a8400) 0 

Class X 
    size=8 align=4 
    base size=8 base align=4 
X (0x19a8440) 0 
    Base (0x19a8480) 0 

Class Y 
    size=8 align=4 
    base size=8 base align=4 
Y (0x19a84c0) 0 
    Base (0x19a8500) 0 

Class Z 
    size=16 align=4 
    base size=16 base align=4 
Z (0x19b1800) 0 
    X (0x19a8540) 0 
    Base (0x19a8580) 0 
    Y (0x19a85c0) 8 
    Base (0x19a8600) 8 

Preste especial atención al argumento "tamaño de base". Ahora el caso virtuals, y mostrando solamente Z:

Class Z 
    size=20 align=4 
    base size=16 base align=4 
Z (0x19b3000) 0 
    vptridx=0u vptr=((& Z::_ZTV1Z) + 12u) 
    X (0x19a8840) 0 
     primary-for Z (0x19b3000) 
     subvttidx=4u 
    Base (0x19a8880) 16 virtual 
     vbaseoffset=-0x0000000000000000c 
    Y (0x19a88c0) 8 
     subvttidx=8u vptridx=12u vptr=((& Z::_ZTV1Z) + 24u) 
    Base (0x19a8880) alternative-path 

Nota del "tamaño de la base" es el mismo, pero el "tamaño" es un puntero más, y tenga en cuenta que ahora hay un puntero vtable! Esto a su vez contiene los vtables de construcción para las clases de padres y toda la magia entre clases (vtables construcción y tabla tabla virtual (VTT)), tal como se describe aquí:

http://www.cse.wustl.edu/~mdeters/seminar/fall2005/mi.html

Tenga en cuenta que la función real dispatch vtable estará vacío.

+0

pero si ninguna de las clases tiene una función virtual, ¿por qué el vtable? – wilhelmtell

+0

Ejecute el código a través de g ++ con esa opción y verá por qué. Mira el enlace que di para entenderlo todo; ¡Mucho mejor que yo tratando de volver a criticarlo aquí! – user23167

+0

¿Qué significa "base" en "tamaño base = 16 base alinear = 4"? ¿Significa todas las clases base? – ibread

Cuestiones relacionadas