empty_bases

Specyficzne dla firmy Microsoft

Standard C++ wymaga, aby najbardziej pochodny obiekt miał rozmiar niezerowy i musi zajmować co najmniej jeden bajt magazynu. Ponieważ wymaganie dotyczy tylko większości obiektów pochodnych, podobiekty klasy bazowej nie podlegają temu ograniczeniu. Pusta optymalizacja klas bazowych (EBCO) wykorzystuje tę wolność. Powoduje to zmniejszenie zużycia pamięci, co może poprawić wydajność. Kompilator Microsoft Visual C++ historycznie miał ograniczoną obsługę EBCO. W programie Visual Studio 2015 Update 3 i nowszych wersjach dodaliśmy nowy __declspec(empty_bases) atrybut dla typów klas, które w pełni korzystają z tej optymalizacji.

Ważne

__declspec(empty_bases) Użycie elementu może spowodować zmianę powodującą niezgodność ABI w strukturze i układzie klasy, w którym jest stosowany. Upewnij się, że cały kod klienta używa tych samych definicji struktur i klas co kod podczas korzystania z tego atrybutu klasy magazynu.

Składnia

__declspec( empty_bases )

Uwagi

W programie Visual Studio, bez żadnych __declspec(align()) specyfikacji, alignas() pusta klasa ma rozmiar 1 bajt:

struct Empty1 {};
static_assert(sizeof(Empty1) == 1, "Empty1 should be 1 byte");

Klasa z pojedynczym elementem niestacjonanym typu char danych ma również rozmiar 1 bajtów:

struct Struct1
{
  char c;
};
static_assert(sizeof(Struct1) == 1, "Struct1 should be 1 byte");

Połączenie tych klas w hierarchii klas powoduje również klasę o rozmiarze 1 bajtu:

struct Derived1 : Empty1
{
  char c;
};
static_assert(sizeof(Derived1) == 1, "Derived1 should be 1 byte");

Ten wynik jest pustą optymalizacją klasy bazowej w pracy, ponieważ bez Derived1 niej będzie to 2 bajty rozmiaru: 1 bajt dla Empty1 i 1 bajt dla .Derived1::c Układ klasy jest również optymalny, gdy istnieje łańcuch pustych klas:

struct Empty2 : Empty1 {};
struct Derived2 : Empty2
{
  char c;
};
static_assert(sizeof(Derived2) == 1, "Derived2 should be 1 byte");

Jednak domyślny układ klasy w programie Visual Studio nie korzysta z funkcji EBCO w wielu scenariuszach dziedziczenia:

struct Empty3 {};
struct Derived3 : Empty2, Empty3
{
  char c;
};
static_assert(sizeof(Derived3) == 1, "Derived3 should be 1 byte"); // Error

Chociaż Derived3 rozmiar może wynosić 1 bajt, domyślny układ klasy powoduje, że rozmiar wynosi 2 bajty. Algorytm układu klasy dodaje 1 bajt wypełnienia między dwoma kolejnymi pustymi klasami podstawowymi, co skutecznie powoduje Empty2 użycie dodatkowego bajtu w ramach :Derived3

class Derived3  size(2):
   +---
0  | +--- (base class Empty2)
0  | | +--- (base class Empty1)
   | | +---
   | +---
1  | +--- (base class Empty3)
   | +---
1  | c
   +---

Skutki tego nieoptymalnego układu są złożone, gdy wymagania dotyczące wyrównania późniejszej klasy bazowej lub podobiektu członkowskiego wymuszają dodatkowe wypełnienie:

struct Derived4 : Empty2, Empty3
{
  int i;
};
static_assert(sizeof(Derived4) == 4, "Derived4 should be 4 bytes"); // Error

Naturalne wyrównanie dla obiektu typu int wynosi 4 bajty, więc 3 bajty dodatkowego wypełnienia należy dodać po Empty3 , aby poprawnie wyrównać Derived4::i:

class Derived4 size(8):
   +---
0  | +--- (base class Empty2)
0  | | +--- (base class Empty1)
   | | +---
   | +---
1  | +--- (base class Empty3)
   | +---
   | <alignment member> (size=3)
4  | i
   +---

Innym problemem z domyślnym układem klasy jest to, że pusta klasa bazowa może zostać ułożone z przesunięciem obok końca klasy:

struct Struct2 : Struct1, Empty1
{
};
static_assert(sizeof(Struct2) == 1, "Struct2 should be 1 byte");
class Struct2 size(1):
   +---
0  | +--- (base class Struct1)
0  | | c
   | +---
1  | +--- (base class Empty1)
   | +---
   +---

Chociaż Struct2 jest to optymalny rozmiar, jest określony z przesunięciem 1 w obrębieStruct2, Empty1 ale rozmiar Struct2 nie jest zwiększany, aby go uwzględnić. W związku z tym w przypadku tablicy Struct2A obiektów adres Empty1 podobiektu A[0] obiektu będzie taki sam jak adres A[1]obiektu , który nie powinien mieć zastosowania. Ten problem nie występuje, gdyby Empty1 został ułożony z przesunięciem 0 w obrębie Struct2obiektu , co powoduje nakładanie się na Struct1 podobiekt.

Domyślny algorytm układu nie został zmodyfikowany w celu rozwiązania tych ograniczeń i pełnego wykorzystania funkcji EBCO. Taka zmiana spowoduje przerwanie zgodności binarnej. Jeśli domyślny układ klasy został zmieniony w wyniku EBCO, każdy plik obiektu i biblioteka zawierająca definicję klasy muszą zostać ponownie skompilowane, aby wszyscy zgodzili się na układ klasy. To wymaganie obejmuje również biblioteki uzyskane ze źródeł zewnętrznych. Deweloperzy takich bibliotek musieliby zapewnić niezależne wersje kompilowane zarówno z układem EBCO, jak i bez niego, aby obsługiwać klientów korzystających z różnych wersji kompilatora. Chociaż nie możemy zmienić układu domyślnego, możemy zapewnić metodę zmiany układu na podstawie klasy z dodatkiem atrybutu __declspec(empty_bases) klasy. Klasa zdefiniowana za pomocą tego atrybutu może w pełni korzystać z EBCO.

struct __declspec(empty_bases) Derived3 : Empty2, Empty3
{
  char c;
};
static_assert(sizeof(Derived3) == 1, "Derived3 should be 1 byte"); // No Error
class Derived3  size(1):
   +---
0  | +--- (base class Empty2)
0  | | +--- (base class Empty1)
   | | +---
   | +---
0  | +--- (base class Empty3)
   | +---
0  | c
   +---

Wszystkie podobiekty Derived3 obiektu są ułożone z przesunięciem 0, a jego rozmiar jest optymalnym 1 bajtem. Należy pamiętać o tym, że __declspec(empty_bases) ma wpływ tylko na układ klasy, do której jest ona stosowana. Nie jest stosowana rekursywnie do klas bazowych:

struct __declspec(empty_bases) Derived5 : Derived4
{
};
static_assert(sizeof(Derived5) == 4, "Derived5 should be 4 bytes"); // Error
class Derived5  size(8):
   +---
0  | +--- (base class Derived4)
0  | | +--- (base class Empty2)
0  | | | +--- (base class Empty1)
   | | | +---
   | | +---
1  | | +--- (base class Empty3)
   | | +---
   | | <alignment member> (size=3)
4  | | i
   | +---
   +---

Mimo że __declspec(empty_bases) jest stosowany do Derived5klasy , nie kwalifikuje się do EBCO, ponieważ nie ma żadnych bezpośrednich pustych klas bazowych, więc nie ma żadnego wpływu. Jeśli jednak zostanie zastosowana do klasy bazowej Derived4 , która kwalifikuje się do EBCO, zarówno, jak Derived4 i Derived5 będzie mieć optymalny układ:

struct __declspec(empty_bases) Derived4 : Empty2, Empty3
{
  int i;
};
static_assert(sizeof(Derived4) == 4, "Derived4 should be 4 bytes"); // No Error

struct Derived5 : Derived4
{
};
static_assert(sizeof(Derived5) == 4, "Derived5 should be 4 bytes"); // No Error
class Derived5  size(4):
   +---
0  | +--- (base class Derived4)
0  | | +--- (base class Empty2)
0  | | | +--- (base class Empty1)
   | | | +---
   | | +---
0  | | +--- (base class Empty3)
   | | +---
0  | | i
   | +---
   +---

Ze względu na wymaganie, aby wszystkie pliki obiektów i biblioteki zgadzały się z układem klasy, __declspec(empty_bases) można stosować tylko do klas, które kontrolujesz. Nie można jej zastosować do klas w bibliotece standardowej ani klas uwzględnionych w bibliotekach, które nie są również ponownie komilowane z układem EBCO.

END Microsoft Specific

Zobacz też

__declspec
Słowa kluczowe