Optymalizacja pustej bazy jest świetna. Obejmuje jednak następujące ograniczenie:
Optymalizacja pustej bazy jest zabroniona, jeśli jedna z pustych klas bazowych jest również typem lub bazą typu pierwszego niestatycznego elementu danych, ponieważ dwa podstawowe podobiekty tego samego typu muszą mieć różne adresy w reprezentacji obiektu najbardziej pochodnego typu.
Aby wyjaśnić to ograniczenie, rozważ następujący kod. Nie static_assert
powiedzie się. Natomiast zmiana albo dziedziczenia Foo
albo Bar
zamiast Base2
spowoduje uniknięcie błędu:
#include <cstddef>
struct Base {};
struct Base2 {};
struct Foo : Base {};
struct Bar : Base {
Foo foo;
};
static_assert(offsetof(Bar,foo)==0,"Error!");
Rozumiem to zachowanie całkowicie. Co ja nie rozumiem, dlaczego ta konkretna zachowanie istnieje . Został oczywiście dodany z jakiegoś powodu, ponieważ jest to wyraźny dodatek, a nie przeoczenie. Jakie jest tego uzasadnienie?
W szczególności, dlaczego dwa podstawowe podobiekty powinny mieć różne adresy? Powyżej Bar
jest typ i foo
jest zmienną składową tego typu. Nie rozumiem, dlaczego klasa podstawowa ma Bar
znaczenie dla klasy podstawowej typu foo
lub odwrotnie.
Rzeczywiście, w razie czego oczekiwałbym, że &foo
jest taki sam jak adres Bar
instancji, która go zawiera - tak jak jest to wymagane w innych sytuacjach (1) . W końcu nie robię nic szczególnego z virtual
dziedziczeniem, niezależnie od tego, klasy podstawowe są puste, a kompilacja z Base2
pokazuje, że w tym konkretnym przypadku nic się nie psuje.
Ale wyraźnie to rozumowanie jest w jakiś sposób niepoprawne i istnieją inne sytuacje, w których ograniczenie to byłoby wymagane.
Powiedzmy, że odpowiedzi powinny dotyczyć C ++ 11 lub nowszej (obecnie używam C ++ 17).
(1) Uwaga: EBO został zaktualizowany w C ++ 11, a w szczególności stał się obowiązkowy dla StandardLayoutType
s (chociaż Bar
powyżej nie jest a StandardLayoutType
).
źródło
Base *a = new Bar(); Base *b = a->foo;
za==b
, alea
ib
są wyraźnie różne obiekty (być może z różnych metod wirtualnych nadpisania).Odpowiedzi:
Ok, wygląda na to, że cały czas się myliłem, ponieważ dla wszystkich moich przykładów musi istnieć vtable dla obiektu podstawowego, który na początku zapobiegałby optymalizacji pustej bazy. Pozwolę, aby przykłady pozostały ważne, ponieważ uważam, że podają kilka interesujących przykładów, dlaczego unikalne adresy są zwykle dobrą rzeczą.
Po głębszym przestudiowaniu tego nie ma technicznego powodu, aby wyłączyć optymalizację pustej klasy podstawowej, gdy pierwszy element jest tego samego typu co pusta klasa podstawowa. To tylko właściwość bieżącego modelu obiektowego C ++.
Ale w C ++ 20 pojawi się nowy atrybut
[[no_unique_address]]
informujący kompilator, że niestatyczny element danych może nie potrzebować unikalnego adresu (technicznie rzecz biorąc, potencjalnie może on nakładać się na [intro.object] / 7 ).Oznacza to, że (moje podkreślenie)
stąd można „reaktywować” optymalizację pustej klasy podstawowej, nadając pierwszemu członkowi danych atrybut
[[no_unique_address]]
. Dodałem tutaj przykład , który pokazuje, jak działa to (i wszystkie inne przypadki, o których mogłem pomyśleć).Błędne przykłady problemów przez to
Ponieważ wydaje się, że pusta klasa może nie mieć metod wirtualnych, dodam trzeci przykład:
Ale dwa ostatnie połączenia są takie same.
Stare przykłady (prawdopodobnie nie odpowiadają na pytanie, ponieważ puste klasy mogą nie zawierać metod wirtualnych)
Rozważ powyższy kod (z dodanymi wirtualnymi destruktorami) w następującym przykładzie
Ale w jaki sposób kompilator powinien rozróżniać te dwa przypadki?
A może nieco mniej wymyślony:
Ale dwa ostatnie są takie same, jeśli mamy pustą optymalizację klasy podstawowej!
źródło
std::is_empty
na cppreference jest znacznie bardziej rozbudowany. To samo co obecny projekt na węgorzu .dynamic_cast
gdy nie jest polimorficzny (z małymi wyjątkami, które nie są tu istotne).