标准本身不包含“空基类”案例。相反,它说(参见 1.8):
[A] 大多数派生对象应具有非零大小并应占用一个或多个
字节的存储空间。基类子对象的大小可能为零。
还有:
除非对象是 [...] 大小为零的基类子对象,否则该对象的地址就是它占用的第一个字节的地址。如果一个对象是另一个对象的子对象,或者如果至少一个是零大小的基类子对象并且它们属于不同类型,则两个对象 [...] 可能具有相同的地址;否则,它们将具有不同的地址。
并且(第 9 条):
类类型的完整对象和成员子对象应具有非零大小。脚注:基类子对象没有那么受限。
这绝不是说只有空基地才能适应任何类型的布局更改,并为“挤压”布局留出足够的空间:例如:
struct A {}; struct B : A { int x; }; // "equivalent" to { int }
struct X { int a; }; struct Y : X {}; // "equivalent" to { int }
考虑B b; 和Y y;。有可能b的地址、b的A子对象的地址(即&static_cast<A&>(b))和b.x的地址相同,同样y的地址, X-y的子对象,和y.a的地址是一样的。
唯一不起作用的是:
struct S {}; struct T { S s; }; // "equivalent" to { char }
"equivalent" 我的意思是最明智、最节省空间的实现。
一个更有趣的案例如下:
struct Foo { int x; char a; }; // { int, char, char[3] }
struct Bar : Foo { short q; }; // { int, char, char, short }
此示例假定sizeof(int) == 4 和sizeof(short) == 2。出于对齐原因,我们有 sizeof(Foo) == 8,但 sizeof(Bar) 也是 8,尽管有更多的数据成员。
该标准的另一个相关部分是 9.2/13:
分配具有相同访问控制(第 11 条)的(非联合)类的非静态数据成员,以便后面的成员在类对象中具有更高的地址。未指定具有不同访问控制的非静态数据成员的分配顺序 (11)。实现对齐要求可能会导致两个相邻的成员不能立即分配;管理虚拟功能 (10.3) 和虚拟基类 (10.1) 的空间要求也是如此。
最后,9.2/10 说标准布局类在开头没有填充,因此它们的地址等于它们的“初始成员”的地址。由于标准布局要求所有基都为空,或者派生最多的类本身没有数据成员,这意味着标准布局类必须采用一种“空基”优化,而我上面的B和Y布局的初始部分其实是强制的。