Хорошо, кажется, что я все время ошибался, так как для всех моих примеров должна существовать vtable для базового объекта, который не позволял бы начинать пустую базовую оптимизацию. Я оставлю примеры в силе, так как я думаю, что они дают некоторые интересные примеры того, почему уникальные адреса, как правило, полезно иметь.
Изучив все это более подробно, нет технической причины для пустой оптимизации базового класса. быть отключенным, когда первый член того же типа, что и пустой базовый класс. Это просто свойство текущей объектной модели C ++.
Но в C ++ 20 появится новый атрибут [[no_unique_address]]
, который сообщает компилятору о том, что элементу данных, не находящемуся в состоянии c, может не потребоваться уникальный адрес (технически говоря, это , потенциально перекрывающийся) [intro.object] / 7 ).
Это означает, что (выделение мое)
Элемент данных, не являющийся объектом c, может совместно использовать адрес другого элемента данных, не являющегося объектом c, или , который базового класса , [...]
, следовательно, можно «реактивировать» пустую оптимизацию базового класса, дав первому члену данных атрибут [[no_unique_address]]
. Я добавил пример здесь , который показывает, как это (и все другие случаи, которые я мог придумать) работает.
Неправильные примеры проблем из-за этого
Поскольку кажется, что пустой класс может не иметь виртуальных методов, позвольте мне добавить третий пример:
int stupid_method(Base *b) {
if( dynamic_cast<Foo*>(b) ) return 0;
if( dynamic_cast<Bar*>(b) ) return 1;
return 2;
}
Bar b;
stupid_method(&b); // Would expect 0
stupid_method(&b.foo); //Would expect 1
Но последние два вызова одинаковы.
Старые примеры (Вероятно, не отвечайте на вопрос, поскольку пустые классы могут не содержать виртуальные методы, кажется)
Рассмотрите в своем коде выше (с добавленными виртуальными деструкторами) следующий пример
void delBase(Base *b) {
delete b;
}
Bar *b = new Bar;
delBase(b); // One would expect this to be absolutely fine.
delBase(&b->foo); // Whoaa, we shouldn't delete a member variable.
Но как должен компилятор различать guish эти два случая?
И, может быть, чуть менее надуманно:
struct Base {
virtual void hi() { std::cout << "Hello\n";}
};
struct Foo : Base {
void hi() override { std::cout << "Guten Tag\n";}
};
struct Bar : Base {
Foo foo;
};
Bar b;
b.hi() // Hello
b.foo.hi() // Guten Tag
Base *a = &b;
Base *z = &b.foo;
a->hi() // Hello
z->hi() // Guten Tag
Но последние два одинаковы, если мы имеем Оптимизация пустого базового класса!