c++ - Diferencia entre clase y estructura con respecto al relleno y la herencia




c++11 gcc (2)

Todo lo siguiente se realizará en GCC 9.1 utilizando Compiler Explorer , en x86-64, usando -O3 .

Tengo este codigo

struct Base {
    Base() {}
    double foo;
    int bar;
};

struct Derived : public Base {
    int baz;
};

int main(int argc, char** argv)
{
    return sizeof(Derived);
}

https://godbolt.org/z/OjSCZB

Devuelve correctamente 16 , como es de esperar, 8 bytes para foo , y 4 bytes para la bar y 4 bytes para baz . Esto funciona solo porque Derived hereda de la Base y, por lo tanto, no tiene que rellenar después de la bar debido a que Derived es un tipo único que contiene elementos tanto de Base como Derived .

Tengo dos preguntas, como a continuación:

Primera pregunta

Si elimino el constructor explícito de Base() {} , comienza a devolver 24 , en lugar de 16 . Es decir, añade relleno después de bar y baz .

https://godbolt.org/z/0gaN5h

No puedo explicar por qué tener un constructor predeterminado explícito es diferente a tener un constructor predeterminado implícito.

Segunda pregunta

Si luego cambio la struct a class para Base , vuelve a cambiar a 16 . No puedo explicar esto tampoco. ¿Por qué los modificadores de acceso cambiarían el tamaño de la estructura?

https://godbolt.org/z/SCYKwL


Con su clase Base obtendrá 4 bytes de relleno de cola, y lo mismo con la clase Derivada, por eso normalmente debería ser de 24 bytes total para el tamaño de Derived .

Se convierte en 16 bytes, porque su compilador puede reutilizar el relleno de la cola .

Sin embargo, la reutilización del relleno de la cola es problemática con los tipos de POD (todos los miembros públicos, constructores predeterminados, etc.), porque rompe las suposiciones comunes que un programador haría. (Entonces, básicamente, cualquier compilador sano no reutilizará el relleno de cola para los tipos de pod)

Supongamos que los compiladores utilizarían la tail padding reuse para los tipos de POD:

struct Base {
    double foo;
    int bar;
};

struct Derived : Base {
    int baz;
};

int main(int argc, char** argv)
{
    // if your compiler would reuse the tail padding then the sizes would be:
    // sizeof(Base) == 16
    // sizeof(Derived) == 16

    Derived d;
    d.baz = 12;
    // trying to zero *only* the members of the base class,
    // but this would zero also baz from derived, not very intuitive
    memset((Base*)&d, 0, sizeof(Base));

    printf("%d", d.baz); // d.baz would now be 0!
}

Al agregar un constructor explícito a la clase Base, o al cambiar las palabras clave de struct a class , la clase Derived ya no satisface la definición de POD y, por lo tanto, la reutilización del relleno de cola no ocurre.


Todo esto se reduce a si su tipo es un agregado o no. Con

struct Base {
    Base() {}
    double foo;
    int bar;
};

struct Derived : public Base {
    int baz;
};

Base no es un agregado debido al constructor. Cuando elimina el constructor, hace que Base un agregado que, al .com/q/47914612/560648 , significa que gcc no "optimizará" el espacio y el objeto derivado no usará los de la base. relleno de la cola.

Cuando cambias el código a

class Base {
    double foo;
    int bar;
};

struct Derived : public Base {
    int baz;
};

foo y bar ahora son privados (porque las clases tienen accesibilidad privada de manera predeterminada), lo que de nuevo significa que Base ya no es un agregado, ya que los agregados no pueden tener miembros privados. Esto significa que estamos de vuelta a cómo funciona el primer caso.





gcc