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 字节的填充内容,实际上导致 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 字节,因此必须在 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
对象的数组 A
,A[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
基类,则 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 布局重新编译的库中包含的类。
结束 Microsoft 专用
另请参阅
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈