代码之家  ›  专栏  ›  技术社区  ›  Armen Tsirunyan

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

c++
  •  5
  • Armen Tsirunyan  · 技术社区  · 14 年前

    这是今天零大小对象和子对象系列中的第三个问题。 标准显然意味着成员子对象的大小不能为零,而基类子对象的大小可以为零。

    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的示例(参见下面的答案),如果同一类中有多个相同类型的子对象,则不允许零大小的成员子对象。我哪里错了?

    3 回复  |  直到 14 年前
        1
  •  1
  •   Stack Overflow is garbage    14 年前

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

    在@konrad rudolphs示例之后,对于成员对象,它必须检查类型,验证该位置是否不存在相同类型的其他对象,以及 然后 也许应用你的优化。除非成员对象位于包含类的末尾。如果是这样,那么对象的“实际”(非零)大小将超出包含类的末尾,这也是一个错误。这在基类的情况下永远不会发生,因为我们知道基类位于派生类的开头,并且派生类的大小为非零。

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

    我不能直接引用任何零大小成员对象 一定地 休息一下,但我不相信他们也不存在。我已经指出了一些基本类案例中不存在的限制,而且很可能存在更多的限制。所以问题是,语言应该允许多大的复杂性和不确定性 只是为了使一个很少有用的优化成为可能 ?

        2
  •  10
  •   Konrad Rudolph    14 年前

    为什么不允许零大小的成员子对象

    因为同一类型的几个(子)对象可以有相同的地址,这是 被禁止的 根据标准:

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

    这有点合乎逻辑:否则,这两个对象 相同的 对于所有意图和目的(因为对象的标识完全由其类型和内存地址决定),单独声明它们是没有意义的。

        3
  •  2
  •   Doug    14 年前

    我认为你的问题是好的——事实上,前面一些关于空基类优化的文章讨论了“空成员优化”,并特别指出类似的优化可以应用于成员。也许,在有标准之前,有些编译器就这样做了。

    这只是空谈猜测,我没有太多的支持,但我昨天看了一下这些方面的标准。

    C相容性

    在本例中:

    struct X{};
    struct Y{};
    struct Z {
       struct X x;
       struct Y y;
       int i;
    };
    

    Z 将是一个在C++ 03规则下的POD,但不会与C IF兼容 x y 是零大小的子对象。布局兼容性是吊舱存在的原因之一。这个问题不会发生在基类上,因为C没有基类,并且基类类不是C++ 03中的POD,所以所有的BET都是OF:)。 访问者注意到,实际上,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 数组,然后返回,或者在零大小的子对象之间。我见过有人实施 operator= 使用 曼皮西 (我不知道为什么…)这会破坏这类事情。对于基类而不是成员来说,打破这样的东西大概没什么问题。