empty_bases

Блок, относящийся только к системам Майкрософт

Стандарт C++ требует, чтобы наиболее производный объект должен иметь ненулевой размер и должен занимать один или несколько байтов хранилища. Поскольку требование распространяется только на большинство производных объектов, подобъекты базового класса не подлежат этому ограничению. Оптимизация пустого базового класса (EBCO) использует эту свободу. Это приводит к снижению потребления памяти, что может повысить производительность. Компилятор Microsoft Visual C++ исторически имел ограниченную поддержку EBCO. В Visual Studio 2015 с обновлением 3 и более поздних версий мы добавили новый __declspec(empty_bases) атрибут для типов классов, которые используют все преимущества этой оптимизации.

Важно!

__declspec(empty_bases) Использование может привести к изменению структуры и макета класса, в котором он применяется. Убедитесь, что весь клиентский код использует те же определения для структур и классов, что и код при использовании этого атрибута класса хранилища.

Синтаксис

__declspec( empty_bases )

Замечания

В Visual Studio отсутствует любой __declspec(align()) или alignas() спецификации, пустой класс составляет 1 байт в размере:

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

Класс с одним нестатичным элементом данных типа char также равен 1 байтам:

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

Объединение этих классов в иерархии классов также приводит к тому, что класс имеет размер 1 байта:

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

Результатом является оптимизация пустого базового класса, так как без нее Derived1 было бы 2 байта размером: 1 байт для Empty1 и 1 байта.Derived1::c Макет класса также является оптимальным при наличии цепочки пустых классов:

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

Однако макет класса по умолчанию в Visual Studio не использует преимущества EBCO в нескольких сценариях наследования:

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

Хотя Derived3 может быть 1 байт в размере, макет класса по умолчанию приводит к размеру 2 байта. Алгоритм макета класса добавляет 1 байт заполнения между двумя последовательных пустыми базовыми классами, что эффективно приводит к Empty2 потреблению дополнительных байтов в пределах Derived3:

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

Эффекты этого неоптимального макета составляются, когда требования к выравниванию более поздней базовой группы или подобъекта элемента принудительно добавляют дополнительную заполнение:

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

Естественное выравнивание для объекта типа int составляет 4 байта, поэтому 3 байта дополнительных заполнений необходимо добавить после Empty3 правильного выравнивания Derived4::i:

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

Еще одна проблема с макетом класса по умолчанию заключается в том, что пустой базовый класс может быть размещен в смещение за конец класса:

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)
   | +---
   +---

Хотя Struct2 это оптимальный размер, выкладывается на смещение 1 в пределахStruct2, Empty1 но размер Struct2 не увеличивается, чтобы учесть его. В результате для массива A объектов адрес Empty1 подобъекта Struct2A[0] будет совпадать с адресомA[1], который не должен быть в данном случае. Эта проблема не будет возникать, если Empty1 бы они были выложены по смещением 0 в пределах Struct2, тем самым перекрывая вложенный Struct1 объект.

Алгоритм макета по умолчанию не был изменен, чтобы устранить эти ограничения и полностью воспользоваться преимуществами EBCO. Такое изменение приведет к разрыву двоичной совместимости. Если макет по умолчанию для класса изменился в результате EBCO, все файлы объектов и библиотеки, содержащие определение класса, должны быть перекомпилированы, чтобы все они согласились с макетом класса. Это требование также будет распространяться на библиотеки, полученные из внешних источников. Разработчикам таких библиотек необходимо предоставить независимые версии, скомпилированные как с макетом EBCO, так и без него, чтобы поддерживать клиентов, использующих разные версии компилятора. Хотя мы не можем изменить макет по умолчанию, мы можем предоставить средства для изменения макета на основе каждого класса с добавлением атрибута __declspec(empty_bases) класса. Класс, определенный этим атрибутом, может полностью использовать 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
   +---

Все подобъекты Derived3 выкладываются на смещение 0, и его размер является оптимальным 1 байтом. Важно помнить, что __declspec(empty_bases) только влияет на макет класса, к которому он применяется. Он не применяется рекурсивно к базовым классам:

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
   | +---
   +---

Хотя __declspec(empty_bases) он применяется Derived5к, он не имеет права на EBCO, так как он не имеет прямых пустых базовых классов, поэтому он не имеет эффекта. Однако если вместо этого он применяется к базовому Derived4 классу, который имеет право на EBCO, оба Derived4 и Derived5 будет иметь оптимальный макет:

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
   | +---
   +---

Из-за требования, что все файлы объектов и библиотеки согласуются с макетом класса, __declspec(empty_bases) можно применять только к классам, которые вы управляете. Его нельзя применять к классам в стандартной библиотеке или к классам, включенным в библиотеки, которые также не компилируются с макетом EBCO.

Завершение блока, относящегося только к системам Майкрософт

См. также

__declspec
Ключевые слова