Должен ли пустой базовый класс влиять на макет производного класса?

Стандарт C ++ (цитата из проекта n3242) говорит о подобъектах [intro.object] следующее:

Если объект не является битовым полем или подобъектом базового класса нулевого размера, адрес этого объекта является адресом первого байта, который он занимает. Два отдельных объекта, которые не являются ни битовыми полями, ни подобъектами базового класса нулевого размера, должны иметь разные адреса.

Теперь, учитывая следующий фрагмент:

struct empty { };
struct member: empty { };
struct derived: empty { member m; };

int main(void)
{
    printf("%d", sizeof(derived));
    return 0;
}

gcc, как мне кажется, распечатывает 2, а Visual C ++ 2010 распечатывает 1. Я подозреваю, что gcc использует стандарт, означающий, что вы не можете использовать псевдоним хранилища типов, если они представляют разные объекты. И я уверен, что MSVC использует стандарт, означающий, что если один подобъект имеет нулевой размер, вы можете делать все, что захотите.

Это неопределенное поведение?


person MSN    schedule 13.10.2011    source источник
comment
Какая версия gcc? gcc 4.7 выводит здесь 1, когда member установлен в char.   -  person evnu    schedule 14.10.2011
comment
@envu, версия, указанная другим SDK.   -  person MSN    schedule 14.10.2011
comment
Если члены имеют разные типы (пустые и символьные), они могут иметь один и тот же адрес. Если они одного типа, они не могут, потому что это нарушит тесты на идентичность объекта, такие как if (this != &that).   -  person Bo Persson    schedule 14.10.2011
comment
Я думаю, что Бо Перссон прав. Насколько я понимаю, поведение VC не соответствует. N3290 10/8 и C ++ 03 10/7 говорят: two subobjects that have the same class type and that belong to the same most derived object must not be allocated at the same address   -  person Ise Wisteria    schedule 14.10.2011
comment
@BoPersson, ты можешь дать ответ? Учитывая формулировку 10/8, я думаю, что это наиболее подходящая интерпретация.   -  person MSN    schedule 14.10.2011
comment
@MSN - Хорошо, я немного расширил комментарий в качестве ответа.   -  person Bo Persson    schedule 14.10.2011


Ответы (4)


Расширяя мой предыдущий комментарий:

Объект идентифицируется по его адресу. Если вы сравниваете адреса (например, указатели) двух объектов одного типа, и они сравниваются как равные, считается, что указатели указывают на один и тот же объект.

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

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

Итак, если члены имеют разные типы (пустые и char), они могут иметь один и тот же адрес. Если они одного типа, они не могут, потому что это нарушит тесты на идентичность объекта, такие как if (this != &that), иногда используемые для тестирования таких вещей, как самоназначение.

Кстати, Microsoft соглашается, что это ошибка в их компиляторе, но есть другие, более срочные вещи, которые нужно исправить в первую очередь.

person Bo Persson    schedule 14.10.2011
comment
Между прочим, стандарт запрещает классам standard-layout иметь базовый класс того же типа, что и первый член, предположительно именно по этой причине. - person Kerrek SB; 23.02.2012

Это зависит от реализации.

Стандарт явно разрешает оптимизацию пустой базы, но не требует ее. Фактически, стандарт ничего не требует о компоновке классов в памяти, только то, что определенные классы будут совместимы по компоновке друг с другом (но не то, что является общей компоновкой). Также указывается порядок членов (когда нет промежуточного спецификатора доступности), но разрешены отступы, заголовки, нижние колонтитулы и всевозможные странные вещи.

person Ben Voigt    schedule 13.10.2011

В окончательной версии стандарта C ++ 11 этот абзац был изменен следующим образом:

Если объект не является битовым полем или подобъектом базового класса нулевого размера, адрес этого объекта является адресом первого байта, который он занимает. Два объекта, которые не являются битовыми полями, могут иметь один и тот же адрес, если один является подобъектом другого или если хотя бы один является подобъектом базового класса нулевого размера, и они относятся к разным типам; в противном случае у них должны быть разные адреса.

Хотя я не уверен, что понимаю, какое это имеет отношение к размерам объектов.

person Nemo    schedule 13.10.2011
comment
Связанный с этим вопрос будет в моем примере: производный d; printf (% d, static_cast ‹пусто *› (& d) == static_cast ‹пусто *› (& d.m)); напечатать 1 или 0? Это определяет, будет ли размер 1 или ›1. - person MSN; 14.10.2011

В этой ветке есть хорошие объяснения. Я просто хотел добавить, что для решения этой проблемы раздувания структуры вы можете просто сделать empty class шаблоном, чтобы создание его экземпляра с другим аргументом шаблона сделало его другим классом:

template<class T>
struct empty { };
struct member: empty<member> { };
struct derived: empty<derived> { member m; };

int main(void)
{
    printf("%d\n", sizeof(derived));
    return 0;
}

Выходы 1.

Это причина избегать использования boost::noncopyable в больших проектах.

person Maxim Egorushkin    schedule 14.10.2011