繁体   English   中英

零大小的成员子对象。 为什么不?

[英]Member subobjects of zero size. Why not?

这是今天零系列对象和子对象系列中的第三个问题。 标准明确暗示成员子对象不能具有零大小,而基类子对象可以。

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

为什么不允许零大小的成员子对象只是像零大小的基类子对象?

TIA

在Konrad的回答之后编辑:考虑以下示例:

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;
};

Z将是C ++ 03规则下的POD,但如果 xy是零大小的子对象,则不会与C布局兼容。 C布局兼容性是POD存在的原因之一。 基类不会发生这个问题,因为C没有基类,而且基类的类不是C ++ 03中的POD,所以所有的赌注都是关闭的:)。 访客注意到C实际上不支持空结构。 所以这整个论点都是错误的。 我只是假设它确实 - 它似乎是一种无害的概括。

此外,程序似乎假设的东西,比如y具有比x更大的地址,以及类似的东西 - 这是由5.10 / 2中指针的关系运算符保证的。 我真的不知道是否有令人信服的理由允许这个,或者有多少程序在实践中使用它。

IMO,这是所有这些中最有力的论据。

不适用于数组

继续上面的例子,添加:

struct Z1 {
   struct X x[1];
   struct Y y[1];
   int i;
};

...人们可能期望sizeof(Z1) == sizeof(Z) ,并且xy也像普通数组那样工作(即你可以形成一个过去的结束指针,并且该指针与任何指针不同元素的地址)。 其中一个期望将被零大小的子对象打破。

不如基本情况引人注目

从空基础派生的主要原因之一是因为它是策略或接口类型类。 这些通常是空的,并且要求它们占用空间会产生“抽象惩罚”,即使更好的组织代码更加臃肿。 这是Stroustrup在C ++中不需要的东西 - 他希望以最小的运行时成本进行适当的抽象。

另一方面,如果声明一个类型的成员,则不继承其函数,typedef等; 并且你没有得到从派生到基数的特殊指针转换,所以也许没有理由拥有零大小的成员而不是零大小的基数。

这里的反例类似于STL容器中的Allocator策略类 - 您不一定希望容器从它派生,但是您希望“保持它”,而不会占用开销。

空基类案例涵盖了大多数用途

...如果您担心空间开销,可以使用私有继承而不是声明成员。 它不是那么直接,但你可以或多或少地达到同样的目的。 显然,如果你有很多空的成员,你想占用零空间,这不会很好。

这是另一个特例

有一些微妙的东西不适用于这种优化。 例如,你不能memcpy一个零大小的POD子对象的位转换成char数组,然后回来,或零大小的子对象之间。 我见过人们实现operator= using memcpy (我不知道为什么......)会破坏这种事情。 据推测,为基类而不是成员打破这些事情并不是一个问题。

编译器可以有条件地允许零大小的成员对象以及基类,但是它会更复杂。 无论类型如何,空基类优化始终适用。 每当编译器看到一个类派生自没有数据成员的类时,它就可以使用空基类优化。

在@Konrad Rudolphs示例之后,使用成员对象,它必须检查类型,验证该位置是否存在相同类型的其他对象, 然后可能应用您的优化。 好吧,除非成员对象位于包含类的末尾。 如果是这样,那么对象的“真实”(非零)大小将突出到包含类的末尾,这也是一个错误。 这在基类案例中永远不会发生,因为我们知道基类位于派生类的开头,并且派生类的大小非零。

因此,这样的优化将更复杂,更微妙,并且更可能以意想不到的方式破坏。

我不能引用零大小成员对象肯定会破坏的任何情况,但我不相信它们也不存在。 我已经指出了基类案例中不存在的一些限制,并且很可能存在更多限制。 所以问题是,语言允许多少复杂性和不确定性才能使一个很少有用的优化成为可能

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM