为什么我们需要下面 [dcl.init]/(6.2) 中突出显示的句子?
Why do we need the highlighted sentence below in [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()
,它导致子对象的值初始化。
- 我们遵循 https://eel.is/c++draft/dcl.init#general-8.1.2,这导致
Derived
子对象被零初始化。现在我们终于到了 OP。
if the object is not a base class subobject, each virtual base class subobject is zero-initialized;
我们正在初始化的 Derived
对象 是 Complete
[=78= 的基础 class 子对象].因此我们不零初始化Derived
的VirtualBase
基础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只会被零初始化一次。你的没有。
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()
,它导致子对象的值初始化。- 我们遵循 https://eel.is/c++draft/dcl.init#general-8.1.2,这导致
Derived
子对象被零初始化。现在我们终于到了 OP。
- 我们遵循 https://eel.is/c++draft/dcl.init#general-8.1.2,这导致
if the object is not a base class subobject, each virtual base class subobject is zero-initialized;
我们正在初始化的 Derived
对象 是 Complete
[=78= 的基础 class 子对象].因此我们不零初始化Derived
的VirtualBase
基础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
.
你用 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只会被零初始化一次。你的没有。