В C ++, почему производный класс, который просто содержит объединение с экземпляром своего базового класса, занимает больше памяти, чем размер объединения? - PullRequest
0 голосов
/ 12 мая 2018

В частности, класс, унаследованный от пустого класса, содержащего только объединение, члены которого включают в себя экземпляр базового класса без данных, занимает больше памяти, чем просто объединение. Почему это происходит и есть ли способ избежать излишней памяти?

Следующий код иллюстрирует мой вопрос:

#include <iostream>

class empty_class { };

struct big : public empty_class
{
    union
    {
        int data[3];
        empty_class a;
    };
};

struct small
{
    union
    {
        int data[3];
        empty_class a;
    };
};

int main()
{   
    std::cout << sizeof(empty_class) << std::endl;
    std::cout << sizeof(big)         << std::endl;
    std::cout << sizeof(small)       << std::endl;
}

Вывод этого кода при компиляции с использованием gcc версии 7.3.0, скомпилированной с -std=c++17 (хотя я получаю тот же результат, используя c ++ 11 и c ++ 14), будет:

1
16
12

Я бы ожидал, что классы большой и маленький должны быть одинакового размера; как ни странно, большой занимает больше памяти, чем маленький , хотя они, похоже, содержат одни и те же данные.

Кроме того, даже если размер массива в объединении изменяется, разница между размером большой и маленький составляет 4 байта.

-Edit:

Кажется, что это поведение не специфично для классов с объединенными типами данных. Подобное поведение происходит в других подобных ситуациях, когда производный класс имеет член с типом базового класса. Спасибо тем, кто указал на это.

Ответы [ 2 ]

0 голосов
/ 12 мая 2018

Здесь union - красная сельдь.

Если вы упростите до

struct empty{};

struct big : empty
{
    empty e;
};

, тогда sizeof(big) должно быть больше, чем sizeof(empty).Это связано с тем, что в big есть два объекта типа empty, и поэтому им требуются разные адреса. пустая базовая оптимизация не может быть применена здесь, как это может быть для

struct small : empty
{
    int n;
};

, где можно ожидать, что sizeof(small) будет sizeof(int).

0 голосов
/ 12 мая 2018

Это из-за того, что я называю «правилом уникальной идентичности» C ++. Каждый (живой) объект в C ++ определенного типа T должен всегда иметь адрес, отличный от любого другого живого объекта типа T. Компилятор не может предоставить макет для типа, в котором это правило будет нарушено, когда два разных подобъекта с одинаковым типом T будут иметь одинаковое смещение в макете содержащего их объекта.

Класс big содержит два заметных подобъекта: базовый класс empty_class и анонимный союз, содержащий член empty_class.

Оптимизация пустой базы основана на совмещении «хранилища» для пустого базового класса с другими типами. Как правило, это делается путем присвоения ему того же адреса, что и у родительского класса, что означает, что адрес, как правило, будет таким же, как первый непустой базовый или первый член подобъекта.

Если бы компилятор дал базовому классу empty_class тот же адрес, что и члену объединения, то у вас было бы два отдельных подобъекта класса (big::empty_class и big::a), которые имеют один и тот же адрес, но являются разными объектами.

Такой макет нарушит правило уникальной идентификации. И поэтому компилятор не может использовать здесь пустую базовую оптимизацию. Вот почему big не является стандартным макетом.

...