Share via


empty_bases

Microsoft 专用

C++ Standard 要求大多数情况下为派生的对象的大小不得为零,并且该对象必须占用一个或多个存储字节。 由于此要求仅扩展到大多数情况下为派生的对象,因此基类子对象不受此约束的约束。 空基类优化 (EBCO) 充分利用了这种自由。 这会导致内存消耗减少,因此可提高性能。 Microsoft Visual C++ 编译器在历史上对 EBCO 的支持有限。 在 Visual Studio 2015 Update 3 及更高版本中,我们添加了一个新的 __declspec(empty_bases) 属性,用于充分利用此优化的类类型。

重要

使用 __declspec(empty_bases) 可能会导致应用它的结构和类布局中发生 ABI 中断性变更。 使用此存储类属性时,请确保所有客户端代码对结构和类使用与你的代码相同的定义。

语法

__declspec( empty_bases )

备注

在缺少任何 __declspec(align())alignas() 规范的 Visual Studio 中,空类的大小为 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 字节的填充内容,实际上导致 Empty2Derived3 中消耗一个额外的字节:

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 字节,因此必须在 Empty3 后添加 3 字节的额外填充内容才能将 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 是最佳大小,Empty1 布局时在 Struct2 中的偏移量为 1,但 Struct2 的大小的增加并未考虑到此因素。 因此,对于 Struct2 对象的数组 AA[0]Empty1 子对象的地址将与 A[1] 的地址相同,这种情况是不应该出现的。 如果在 Struct2 中偏移量为 0 处对 Empty1 进行布局,导致 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 的条件,因为它没有任何直接的空基类,因此不起作用。 但是,如果改将其应用于符合 EBCO 条件的 Derived4 基类,则 Derived4Derived5 都将具有最佳布局:

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 布局重新编译的库中包含的类。

结束 Microsoft 专用

另请参阅

__declspec
关键字