diente filoso
Supongamos que tengo una clase base con algunas variables miembro y sin funciones virtuales:
class Base {
int member;
};
y una clase derivada que deriva de forma no virtual de Base
y no tiene nuevas variables miembro ni funciones virtuales:
class Derived : Base {
};
Obviamente sizeof(Derived)
no puede ser más pequeño que sizeof(Base)
.
Es sizeof(Derived)
se requiere que sea igual a sizeof(Base)
?
Desde 5.3.2 [expr.sizeof]
Cuando se aplica a una clase, el resultado [of
sizeof
] es el número de bytes en un objeto de esa clase, incluido cualquier relleno necesario para colocar objetos de ese tipo en una matriz. El tamaño de una clase más derivada será mayor que cero (1.8).
Desde 1.8 [intro.object]
A menos que sea un campo de bits (9.6), un objeto más derivado tendrá un tamaño distinto de cero y ocupará uno o más bytes de almacenamiento. Los subobjetos de la clase base pueden tener un tamaño cero. Un objeto de tipo POD (3.9) ocupará bytes de almacenamiento contiguos.
y una nota:
El tamaño real de un subobjeto de clase base puede ser menor que el resultado de aplicar sizeof al subobjeto, debido a clases base virtuales y requisitos de relleno menos estrictos en subobjetos de clase base.
Junte esto y creo que lo que le está diciendo es que no tiene ninguna garantía de lo que sizeof
podría decirle, aparte de que el resultado será mayor que cero. De hecho, ni siquiera parece garantizar que sizeof(Derived) >= sizeof(Base)
!
-
Esta es mi interpretación también. Por supuesto, en la práctica,
sizeof(Derived)
será mayor o igualsizeof(Base)
pero el estándar no lo garantiza.– James Kanze
13 de noviembre de 2013 a las 9:36
-
@JamesKanze: Creo que la conversión implícita de
Derived&
aBase&
proporciona tal garantía. Ahora, es posible que el tamaño de la clase derivada sea menor que la suma de los tamaños de todas las clases base, porque los subobjetos base pueden superponerse, pero tiene que ser al menos igual al tamaño del subobjeto base más grande.– Ben Voigt
16/10/2014 a las 14:50
-
@BenVoigt ¿Por qué? No puedo pensar en ninguna razón por la que esto no debería cumplirse en la práctica, pero no puedo pensar en ninguna regla en el estándar que lo requiera. O tal vez, si
Derived
se define cuando un#pragma pack
está vigente, peroBase
¿no? (#pragma pack
es, por supuesto, no parte del C++ estándar. Pero presumiblemente, una implementación podría hacer cosas divertidas según el nombre de la clase).– James Kanze
16/10/2014 a las 15:45
-
@JamesKanze: Tienes que poder usar
Derived
a través deBase&
y la única forma en que funciona es si hay un tamaño completoBase
subobjeto para que esa referencia tenga acceso. Los subobjetos pueden superponerse; no se les permite tener tamaño reducido.– Ben Voigt
16/10/2014 a las 15:54
-
@BenVoigt Lo siento, pero no te sigo. ¿Por qué necesita un subobjeto base de tamaño completo para usar?
Base&
como unBase
? Todos los miembros declarados deben estar allí, pero si no hay miembros de datos declarados, ¿qué puede hacer para detectar que la memoria real es menor de lo que espera?– James Kanze
16/10/2014 a las 18:20
No existe tal requisito.
La única parte relevante del lenguaje en la que puedo pensar es que cada objeto, ya sea completo o no, y ya sea derivado o no, tiene una identidad, que viene dado por el par de su dirección y su tipo. Cf. C++11 1.8/6:
Dos objetos que no son campos de bits pueden tener la misma dirección si uno es un subobjeto del otro, o si al menos uno es un subobjeto de clase base de tamaño cero y son de diferentes tipos; en caso contrario, tendrán direcciones distintas.
Entonces, tanto el objeto más derivado como el subobjeto base de su ejemplo deben tener identidades distintas.
Sin duda, tendría sentido que un compilador diera ambos Base
y Derived
un tamaño de 1
, pero esto no es obligatorio. Sería aceptable si el Base
tenía talla 1729 y Derived
Tenía talla 2875.
-
Jeje, me pregunto si el estándar impone alguna restricción a los tipos no primitivos (cuando se trata de su tamaño). Tal vez podría hacer
Base
2875 yDerived
1729 solo para reír =)– luk32
13 de noviembre de 2013 a las 9:26
-
@KerrekSB El
Base
el subobjeto puede ocupar menos bytes quesizeof(Base)
. Esta regla se introdujo porque ninguna clase puede tener unsizeof
de menos de 1, pero se deseaba que las clases base vacías no aumentaran el tamaño del objeto. Sin embargo, tiene algunas consecuencias interesantes: supongamos queBase
contiene solo tipos integrales, y quiero inicializarlo en el constructor conmemset( this, 0, sizeof(*this) )
.– James Kanze
13 de noviembre de 2013 a las 9:41
-
@JamesKanze: Si
Base
contiene miembros de datos, seguramente su tamaño no puede ser cero? Un puntero base apunta a un objeto base, sin importar si está completo o no. La única diferencia de tamaño puede estar fuera de lo que se observa desde dentro del objeto. (¿O quisiste decir queDerived
contiene soloint
¿s?)– KerrekSB
13 de noviembre de 2013 a las 10:02
-
@KerrekSB La “optimización” se introdujo para permitir que las clases vacías no ocupen espacio cuando son una base, y se denomina optimización de clase base vacía, pero la redacción real adoptada en realidad permite que cualquier clase base ocupe una cantidad de bytes diferente de lo que
sizeof
informes. Esto podría tener sentido si tienesstruct Base { int i; char c; }; struct Derived : Base { char c; };
; consideraciones de alineación pueden significar quesizeof(Base) == 8
perosizeof(Derived) == 8
también, a pesarDerived
agregar un miembro de datos.– James Kanze
13 de noviembre de 2013 a las 10:26
-
@JamesKanze: No, el subobjeto base no puede ocupar menos bytes que
sizeof (Base)
. No tiene que contribuir tanto asizeof (Derived)
sin embargo, debido a que los subobjetos de diferentes tipos pueden superponerse, siempre que los miembros de datos reales tengan almacenamiento distinto.– Ben Voigt
16/10/2014 a las 14:51
David
Interesante pregunta. tengo un ejemplo donde una clase derivada con un campo extra es el mismo tamaño como un vacío clase básica. (Esto debería ser un comentario, pero es demasiado grande; acepte una de las otras respuestas, aunque los votos a favor son bienvenidos si es interesante).
Considere este programa trivial de C++:
class A {};
class B : public A {
int m_iInteger;
};
int _tmain(int argc, _TCHAR* argv[])
{
printf("A: %d\r\n", sizeof(A));
printf("B: %d\r\n", sizeof(B));
printf("int: %d\r\n", sizeof(int));
return 0;
}
¿Cuál esperaría que fuera la salida, si sizeof(int)
es 4? Tal vez algo como:
A: 0
B: 4
int: 4
?
mi compilador – Generador C++ de Embarcadero 2010 – da la salida:
A: 8
B: 8
int: 4
En otras palabras, agregar un campo adicional en la clase derivada no hace que la clase derivada sea más grande.
Hay una idea de por qué con el tema del archivo de ayuda en el opción de compatibilidad Clase base vacía de longitud cero.
Por lo general, el tamaño de una clase es de al menos un byte, incluso si la clase no define ningún miembro de datos. Cuando establece esta opción, el compilador ignora este byte no utilizado para el diseño de la memoria y el tamaño total de las clases derivadas; las clases base vacías no consumen espacio en las clases derivadas. Predeterminado = Falso
Parece que el tamaño de una clase con la configuración del compilador predeterminada para este compilador es de 8 bytes, no de uno, y de hecho cambiar esta configuración para este ejemplo de código no tiene efecto.
También puede encontrar este artículo sobre los tamaños de las clases base y la optimización anterior interesante. Analiza por qué las clases deben tener un tamaño de al menos un byte, qué hace la optimización y profundiza en la representación de las funciones miembro, etc.
De hecho, la norma requiere que el tamaño de un objeto nunca sea cero; también requiere que en un objeto derivado los miembros de datos de la(s) clase(s) base(s) aparezcan antes que los miembros de datos declarados por el usuario de la clase derivada. Sin embargo, un subobjeto de clase base no se considera un objeto completo. Por lo tanto, es posible eliminar el subobjeto de la clase base del objeto derivado sin infringir las reglas. En otras palabras, en el objeto t, el desplazamiento de S y x puede superponerse…
Lea el artículo para conocer el contexto completo de esa cita.
class Base {
// int member; I have just created an empty base class.
};
class Derived : Base {
};
Ahora el compilador gcc daría “tamaño> 0” para los objetos creados por las clases Base y Derivado. El compilador gcc solo proporciona la presencia de la dirección de los objetos al usuario.
Note:Derived class would contain base class members, so obviously we can think of
sizeof(derived class) greater then sizeof(base class). But this depends on the compiler
if it allocates some extra space while defining the derived class.
Mi compilador gcc actual mostró que el tamaño de los objetos de Base y Derivado es el mismo.
Interesante pregunta (+1). No sé la respuesta, pero realmente no veo por qué el estándar haría todo lo posible para requerir este.
– ENP
13 de noviembre de 2013 a las 9:18
Ni siquiera estoy seguro de que haya una garantía en el estándar de que
Derived
no puede ser más pequeño queBase
si se utilizan las reglas de la clase base vacía.– James Kanze
13 de noviembre de 2013 a las 9:34
@AaronMcDaid ¿En la práctica o en la teoría? En la práctica, lo dudo (pero puede agregar miembros de datos a
D
y aún terminar consizeof(D) == sizeof(B)
). Pero la norma no lo impide; uno podría imaginar una implementación en la que el tamaño dependiera de alguna manera del nombre. (Pero estoy bastante seguro de que nunca verá tal implementación, y no me preocupa la pérdida de portabilidad si mi código no tiene en cuenta esta posibilidad).– James Kanze
13 de noviembre de 2013 a las 10:52
Relacionado: stackoverflow.com/q/19843816/420683 (que es un requisito más estricto)
– mojar
13 de noviembre de 2013 a las 12:36
@HansPassant: Lo sé y es por eso que afirmé explícitamente que en esta pregunta no hay métodos virtuales ni herencia virtual.
– diente filoso
13 de noviembre de 2013 a las 13:28