C++ 零大小的成员子对象。为什么不呢?
这是今天零尺寸对象和子对象系列中的第三个问题。 标准清楚地表明,成员子对象的大小不能为零,而基类子对象的大小可以为零C++ 零大小的成员子对象。为什么不呢?,c++,C++,这是今天零尺寸对象和子对象系列中的第三个问题。 标准清楚地表明,成员子对象的大小不能为零,而基类子对象的大小可以为零 struct X {}; //empty class, complete objects of class X have nonzero size struct Y:X { char c; }; //Y's size may be 1 struct Z {X x; char c;}; //Z's size MUST be greater than 1 为什么不允许零大小的成员
struct X {}; //empty class, complete objects of class X have nonzero size
struct Y:X { char c; }; //Y's size may be 1
struct Z {X x; char c;}; //Z's size MUST be greater than 1
为什么不允许零大小的成员子对象,就像零大小的基类子对象一样
短暂性脑缺血发作
康拉德回答后编辑:
考虑下面的例子:
struct X{};
struct D1:X{};
struct D2:D1, X {}; //D2 has 2 distinct subobjects of type X, can they both be 0 size and located at the same address?
如果同一类型X的两个基类子对象(如我的示例中)可以位于同一地址,那么应该能够成为子对象的成员。如果不能,那么编译器会特别处理这种情况,也可以特别处理Konrad的示例(见下面的答案),如果同一类中有多个相同类型的子对象,则不允许零大小的成员子对象。我错在哪里
为什么不允许零大小的成员子对象
因为同一类型的多个(子)对象可能具有相同的地址,这是标准所禁止的:
struct X { virtual ~X() { /* Just so we can use typeid! */ } };
struct Y {
X a;
X b;
};
Y y;
// The standard requires that the following holds:
assert(typeid(y.a) != typeid(y.b) or &y.a != &y.b);
这有点合乎逻辑:否则,这两个对象在所有意图和目的上都是相同的(因为对象的标识完全由其类型和内存地址决定),单独声明它们是没有意义的。我认为你的问题很好-事实上,早期一些关于空基类优化的文章谈到了“空成员优化”,并明确指出类似的优化可以应用于成员。也许,在标准出现之前,有些编译器就这样做了 这只是空泛的猜测,我没有太多的支持,但我昨天看了标准的一些方面 C兼容性 在本例中:
struct X{};
struct Y{};
struct Z {
struct X x;
struct Y y;
int i;
};
根据C++03规则,Z
将是一个POD,但如果x
和y
是零大小的子对象,则布局与C不兼容。C布局兼容性是PODs存在的原因之一。这个问题不会发生在基类上,因为C没有基类,而在C++03中有基类的类不是pod,所以所有的赌注都是无效的:)。访问者注意到C实际上不支持空结构。所以整个论点都是错误的。我只是假设是这样的——这似乎是一个无害的概括
此外,程序似乎假设了一些事情,比如y
的地址大于x
,诸如此类的事情——这是由5.10/2中指针上的关系运算符保证的。我真的不知道是否有令人信服的理由允许这样做,或者有多少程序在实践中使用它
依我看,这是所有这些中最有力的论点
不能很好地推广到数组
继续上面的示例,添加以下内容:
struct Z1 {
struct X x[1];
struct Y y[1];
int i;
};
…人们可能期望sizeof(Z1)=sizeof(Z)
,并且x
和y
也可以像普通数组一样工作(即,可以形成一个超过结束指针的指针,该指针与任何元素的地址都不同)。这些期望之一将被零大小的子对象打破
没有基本情况那么令人信服
从空基派生的主要原因之一是因为它是策略或接口类型类。这些代码通常是空的,要求它们占用空间会造成“抽象惩罚”,即使组织更好的代码更加臃肿。这是Stroustrup在C++中不需要的——他希望在最小运行时成本下适当的抽象。
另一方面,如果声明类型的成员,则不会继承其函数、typedef等;而且您不会得到从派生到基的特殊指针转换,因此可能没有理由使用零大小的成员而不是零大小的基
这里的一个反例类似于STL容器中的分配器策略类——您不一定希望容器派生自它,但您希望“保持它”而不占用开销
空基类案例涵盖了大多数用途
…如果担心空间开销,可以使用私有继承而不是声明成员。虽然没有那么直接,但你或多或少也能达到同样的效果。显然,如果您有很多空成员,并且希望占用零空间,那么这就不能很好地工作
这是另一个特例
有相当多的微妙的事情,不与此优化工作。例如,您不能memcpy
将零大小的POD子对象的位放入char
数组,然后返回,或在零大小的子对象之间返回。我见过有人用memcpy
实现operator=
(我不知道为什么…),这会破坏这类东西。假定为基类而不是成员破坏这些东西的问题不大。编译器当然可以有条件地允许零大小的成员对象和基类,但这会更复杂。无论类型如何,空基类优化始终适用。每当编译器看到一个类派生自一个没有数据成员的类时,它都可以使用空基类优化
按照@Konrad Rudolphs的示例,对于成员对象,它必须检查类型,验证该位置不存在其他相同类型的对象,然后可能应用您的优化。除非成员对象位于包含类的末尾。如果是这样,那么对象的“真实”(非零)大小将超出包含类的末尾,这也是一个错误。这在基类的情况下永远不会发生,因为我们知道基类位于派生类的开头,并且派生类的大小非零
因此,这样的优化将更加复杂,更加微妙,更可能以意想不到的方式中断
我不能马上举出任何零尺寸成员对象肯定会断裂的例子,但我不相信它们也不存在。我已经指出了一些限制