为什么我们需要下面 [dcl.init]/(6.2) 中突出显示的句子?

Why do we need the highlighted sentence below in [dcl.init]/(6.2)?

[dcl.init]/(6.2)

If T is a (possibly cv-qualified) non-union class type, its padding bits are initialized to zero bits and each non-static data member, each non-virtual base class subobject, and, if the object is not a base class subobject, each virtual base class subobject is zero-initialized;

AFAICT,上面这句话很容易混淆,可有可无。不是基础 class 子对象的 对象 是什么意思?

“对象”的类型为 T。这句话简单地将初始化virtual bases的逻辑应用到零初始化。它始终是初始化虚拟基的最派生对象。例如.

class A {
  //
};

class B : public virtual A {
  //
};

class C : public B {
  //
};

static B b; // The zero initialization of `b` also zero initializes an `A` sub-object
static C c; // The zero initialization of the 'B' sub-object **does not** include
            // zero-initialization of an `A` sub-object.

突出显示的句子在这里有用,并不是多余的。

示例 A:

#include <iostream>

struct VirtualBase
{
    int x;
};

struct Derived : virtual VirtualBase
{};

struct Complete : Derived, virtual VirtualBase
{
    Complete()
        : VirtualBase{42}
        , Derived()
    {}
};

int main()
{
    Complete c;
    std::cout << c.x;
}

Complete c; 的初始化如何工作?

  • 我们从 Complete 对象的默认初始化开始。这会调用默认的构造函数。
  • default-ctor 从初始化虚拟基 classes (class.base.init) 开始。
    • 我们对 VirtualBase 子对象进行聚合初始化,并将 int 设置为 42
  • 然后,我们初始化 Derived 子对象。初始化的形式是 Derived() ,它导致子对象的值初始化。

if the object is not a base class subobject, each virtual base class subobject is zero-initialized;

我们正在初始化的 Derived 对象 Complete[=78= 的基础 class 子对象].因此我们零初始化DerivedVirtualBase基础class对象。

如果没有子句 “如果对象不是基础 class 子对象”,我们将零初始化 VirtualBase 子对象 Derived。这意味着零初始化 int 成员,将其设置为 0。因为这种情况发生在 之后,我们已经将 42 写入其中,将其设置为 0 会覆盖该值。


示例 B:

#include <iostream>

struct VirtualBase
{
    int x;
};

struct Derived : virtual VirtualBase
{};

struct Complete : Derived, virtual VirtualBase
{
    Complete()
        : Derived()
    {}
};

int main()
{
    Complete c;
    std::cout << c.x;
}

Complete 的构造函数缺少 VirtualBase 的任何内存初始化器,因此它将 默认初始化 虚拟基 class。另一方面,Derived 是值初始化的,这会导致零初始化。由于 Derived 在这里是基础 class 子对象,因此它将 而不是 零初始化虚拟基础。它保持未初始化状态,我们在 cout << c.x.

中得到 UB

你用 gcc 和 clang 得到了一些有趣的输出,但不幸的是我无法让消毒剂报告它。 Live demo

编写技术规范有点像编程。 [dcl.init]/6 因此可以被认为是一种函数:对对象执行零初始化的函数。当一个完整的对象被零初始化时调用此函数。

下面是 [dcl.init]/6 的一般结构的一些伪代码:

void zero_init(T &t)
{
  if(zero_init_1(t)) return; //Executes rule 6.1; returns false if rule 6.1 doesn't apply to T.
  if(zero_init_2(t)) return; //Executes rule 6.2; returns false if rule 6.2 doesn't apply to T.
  if(zero_init_3(t)) return; //Executes rule 6.3; returns false if rule 6.3 doesn't apply to T.
  ...
}

这里重要的是zero_init_2,代表第6.2节的代码:

bool zero_init_2(T &t)
  if(T is class && T is !union)
  {
    zero_init_padding_bit(t);
    for(auto &base : non_virtual_bases(t))
      zero_init(base);
    for(auto &member : members(t))
      zero_init(member);

    if(!is_base_class(t))
      for(auto &base : virtual_bases(t))
        zero_init(base);

    return true; //I did initialization here.
  }
  return false; //No initialization done here.
}

您建议:

I would replace "and, if the object is not a base class subobject, each virtual base class subobject is zero-initialized" by "and each virtual base class subobject of the most derived object is zero-initialized".

代码如下:

bool zero_init_2(T &t)
  if(T is class && T is !union)
  {
    zero_init_padding_bit(t);
    for(auto &base : non_virtual_bases(t))
      zero_init(base);
    for(auto &member : members(t))
      zero_init(member);

    auto &u = get_most_derived_object(t)
    for(auto &base : virtual_bases(u))
      zero_init(base);

    return true; //I did initialization here.
  }
  return false; //No initialization done here.
}

在您建议的版本中,没有条件。所以伪代码中也没有条件。但是 zero_init 是一个可以调用 zero_init_2 的函数;也就是说,它是递归.

这意味着虚拟基 classes 可以被多次零初始化。实际上,它在任何具有虚基 classes 的类型上都是无限递归的。 get_most_derived_object 将 return 派生最多的对象。该对象的非联合虚拟基础 class 子对象包括……我们当前正在初始化的对象。所以我们将递归地重新初始化自己,永不终止。

原来的措辞确保虚拟基classes只会被零初始化一次。你的没有。