Разница между классом и структурой в отношении заполнения и наследования

Все нижеприведенное будет сделано на GCC 9.1 с использованием Compiler Explorer, в x86-64 с использованием -O3.

У меня есть этот код:

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

Он правильно возвращает 16, как и следовало ожидать, 8 байтов для foo, 4 байта для bar и 4 байта для baz. Это работает только потому, что Derived наследуется от Base и поэтому ему не нужно заполнять после bar потому что Derived является одним типом, содержащим элементы Base и Derived.

У меня есть два вопроса, как показано ниже:

Первый вопрос

Если я уберу явный конструктор Base() {}, он начнет возвращать 24 вместо 16. то есть он добавляет отступы после bar и baz.

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

Я не могу объяснить, почему наличие явного конструктора по умолчанию отличается от наличия неявного конструктора по умолчанию.

Второй вопрос

Если я затем изменю struct на class для Base, он вернется к возвращению 16. Я тоже не могу это объяснить. Почему модификаторы доступа могут изменить размер структуры?

https://godbolt.org/z/SCYKwL

Ответ 1

Это все сводится к тому, является ли ваш тип совокупным или нет. С

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

struct Derived : public Base {
    int baz;
};

Base не является агрегатом из-за конструктора. Когда вы удаляете конструктор, вы делаете Base агрегатом, который, согласно добавлению конструктора по умолчанию к базовому классу, изменяет sizeof() производного типа, что означает, что gcc не будет "оптимизировать" пространство, а производный объект не будет использовать base обивка хвоста.

Когда вы меняете код на

class Base {
    double foo;
    int bar;
};

struct Derived : public Base {
    int baz;
};

foo и bar теперь являются частными (поскольку классы имеют закрытую доступность по умолчанию), что снова означает, что Base больше не является агрегатом, поскольку агрегатам не разрешено иметь закрытых членов. Это означает, что мы вернулись к тому, как работает первый случай.

Ответ 2

С вашим базовым классом вы получите 4 байта дополнения хвостом, и то же самое с классом Derived, поэтому обычно он должен составлять всего 24 bytes для размера Derived.

Он становится 16 байтов, потому что ваш компилятор может повторно использовать заполнение хвостом.

Однако повторное использование хвостового отступа проблематично для типов POD (все члены public, конструктор по умолчанию и т.д.), Потому что это нарушает общие предположения, которые может сделать программист. (Таким образом, в принципе, любой здравомыслящий компилятор не будет использовать повторное использование хвостового отступа для типов pod).

Пусть притворятся, что компиляторы будут использовать tail padding reuse использование tail padding reuse для типов 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!
}

При добавлении явного конструктора к базовому классу или при изменении ключевых слов struct на class Derived больше не удовлетворяет определению POD и, следовательно, повторное использование хвостового отступа не происходит.

Ответ 3

Нет разницы между структурой и классом. Они оба могут иметь конструкторы, методы (даже виртуальные), публичные, частные и защищенные члены, использовать наследование и быть шаблонными.

Единственное отличие состоит в том, что если вы не укажете видимость (публичную, приватную или защищенную) членов, они будут публичными в структуре и приватными в классе.

Возможно, проблема зависит от характера ваших частных/публичных членов (распределение/конфликт)