В С++ большинство оптимизаций выведены из правила as-if. То есть, пока программа ведет себя так: если оптимизация не состоялась, то они действительны.
Оптимизация пустых баз данных является одним из таких трюков: в некоторых условиях, если базовый класс пуст (не имеет какого-либо нестатического элемента данных), то компилятор может лидировать в представлении своей памяти.
По-видимому, кажется, что стандарт запрещает эту оптимизацию для членов данных, то есть даже если элемент данных пуст, он все равно должен занимать не менее одного байта: от n3225, [class]
4 - Завершенные объекты и субобъекты класса типа класса должны иметь ненулевой размер.
Примечание. Это приводит к использованию частного наследования для разработки политики, чтобы при необходимости активировать EBO
Мне было интересно, можно ли с помощью правила as-if выполнить эту оптимизацию.
изменить: следуя нескольким ответам и комментариям, а также уточнить, что мне интересно.
Во-первых, позвольте мне привести пример:
struct Empty {};
struct Foo { Empty e; int i; };
Мой вопрос: почему sizeof(Foo) != sizeof(int)
? В частности, если вы не указали какую-либо упаковку, вероятность того, что Foo будет в два раза больше, чем количество int, должно быть связано с проблемами выравнивания, что кажется смехотворным завышенным.
Примечание: мой вопрос не в том, почему это sizeof(Foo) != 0
, на самом деле это не требуется EBO либо
В соответствии с С++ это происходит потому, что не может быть нулевого размера. Однако базе разрешено иметь нулевой размер (EBO), поэтому:
struct Bar: Empty { int i; };
вероятно, (благодаря EBO) подчиняться sizeof(Bar) == sizeof(int)
.
Стив Джессоп, похоже, придерживается мнения, что это так, что ни один из двух под-объектов не будет иметь одинаковый адрес. Я думал об этом, однако в большинстве случаев это фактически не мешает оптимизации:
Если у вас есть "неиспользуемая" память, то это тривиально:
struct UnusedPadding { Empty e; Empty f; double d; int i; };
// chances are that the layout will leave some memory after int
Но на самом деле это даже "хуже", чем это, потому что пространство Empty
никогда не записывается (вам лучше не делать, если EBO вступает...), и поэтому вы можете разместить его на занятом месте, не является адресом другого объекта:
struct Virtual { virtual ~Virtual() {} Empty e; Empty f; int i; };
// most compilers will reserve some space for a virtual pointer!
Или, даже в нашем исходном случае:
struct Foo { Empty e; int i; }; // deja vu!
Можно было бы (char*)foo.e == (char*)foo.i + 1
, если бы все, что мы хотели, было другим адресом.