默认、值和零初始化混乱
Default, value and zero initialization mess
我对值初始化、默认初始化和零初始化感到很困惑。
尤其是当他们针对不同的标准 C++03 和 C++11 (以及 C++14 ).
我在这里引用并尝试扩展一个非常好的答案 Value-/Default-/Zero- Init C++98 and C++03 以使其更通用,因为如果有人可以帮助填补所需的空白以更好地概述什么时候会发生什么?
通过示例概括的完整见解:
有时 new 运算符返回的内存会被初始化,有时不会,这取决于您要更新的类型是 POD (plain old data),还是 class包含 POD 成员并使用编译器生成的默认构造函数。
- 在C++1998中有两种类型的初始化:zero-和default-initialization
- 在C++2003第三种初始化类型中,添加了值初始化。
- 在 C++2011/C++2014 中仅添加了 list-initialization 和 的规则value-/default-/zero-initialization有点变了。
假设:
struct A { int m; };
struct B { ~B(); int m; };
struct C { C() : m(){}; ~C(); int m; };
struct D { D(){}; int m; };
struct E { E() = default; int m;}; /** only possible in c++11/14 */
struct F {F(); int m;}; F::F() = default; /** only possible in c++11/14 */
在 C++98 编译器中,应出现以下情况:
new A
- 不确定值(A
是 POD)
new A()
- 零初始化
new B
- 默认构造(B::m
未初始化,B
是非 POD)
new B()
- 默认构造(B::m
未初始化)
new C
- 默认构造(C::m
为零初始化,C
为非 POD)
new C()
- 默认构造(C::m
为零初始化)
new D
- 默认构造(D::m
未初始化,D
是非 POD)
new D()
- 默认构造?(D::m
未初始化)
在符合 C++03 的编译器中,事情应该像这样工作:
new A
- 不确定值(A
是 POD)
new A()
- 值初始化 A
,这是零初始化,因为它是 POD。
new B
- 默认初始化(B::m
未初始化,B
是非 POD)
new B()
- 值初始化 B
零初始化所有字段,因为它的默认 ctor 是编译器生成的而不是用户定义的。
new C
- 默认初始化 C
,调用默认构造函数。 (C::m
为零初始化,C
为非 POD)
new C()
- 值初始化 C
,调用默认构造函数。 (C::m
为零初始化)
new D
- 默认构造(D::m
未初始化,D
是非 POD)
new D()
- value-initializes D?,调用默认的构造函数(D::m
未初始化)
斜体值和 ?有不明之处,请帮忙改正:-)
在符合 C++11 的编译器中,事情应该像这样工作:
??? (如果我从这里开始,请帮助它无论如何都会出错)
在符合 C++14 的编译器中,事情应该像这样工作:
??? (如果我从这里开始,请帮助它无论如何都会出错)
(根据答案草稿)
new A
- 默认初始化 A
,编译器生成。 ctor, (leavs A::m
未初始化) (A
is POD)
new A()
- 值初始化 A
,这是零初始化,因为 2. 指向 [dcl.init]/8
new B
- 默认初始化 B
,编译器生成。 ctor, (leavs B::m
未初始化) (B
是非 POD)
new B()
- 值初始化 B
零初始化所有字段,因为它的默认 ctor 是编译器生成的而不是用户定义的。
new C
- 默认初始化 C
,调用默认 ctor。 (C::m
为零初始化,C
为非 POD)
new C()
- 值初始化 C
,调用默认构造函数。 (C::m
为零初始化)
new D
- 默认初始化 D
(D::m
未初始化,D
非 POD)
new D()
- 值初始化 D
,调用默认 ctor(D::m
未初始化)
new E
- 默认初始化 E
,调用 comp。将军演员。 (E::m
未初始化,E为非POD)
new E()
- 值初始化 E
,从 [dcl.init] 中的 2 点开始零初始化 E
/8 )
new F
- 默认初始化 F
,调用 comp。将军演员。 (F::m
未初始化,F
非 POD)
new F()
- 值初始化 F
,默认初始化 F
,因为 1. 指向 [dcl.init]/8 (F
ctor 函数是用户提供的,如果它是用户声明的并且在第一次声明时没有明确默认或删除。Link)
C++14 指定在 [expr.new]/17 中使用 new
创建的对象的初始化(在 C++11 中为 [expr.new]/15,注释为当时不是注释而是规范文本):
A new-expression that creates an object of type T
initializes that
object as follows:
- If the new-initializer is omitted, the object is default-initialized (8.5). [ Note: If no initialization is
performed, the object has an indeterminate value. — end note ]
- Otherwise, the new-initializer is interpreted according to the initialization rules of 8.5 for direct-initialization.
default-initialization定义在[dcl.init]/7(C++11中的/6,写法本身也有同样的作用):
To default-initialize an object of type T
means:
- if
T
is a (possibly cv-qualified) class type (Clause 9), the default constructor (12.1) for T
is called (and the initialization
is ill-formed if T
has no default constructor or overload resolution
(13.3) results in an ambiguity or in a function that is deleted or
inaccessible from the context of the initialization);
- if
T
is an array type, each element is default-initialized;
- otherwise, no initialization is performed.
因此
new A
只会导致调用 A
的默认构造函数,而不会初始化 m
。不确定的价值。 new B
. 应该相同
new A()
根据 [dcl.init]/11(C++11 中的 /10)解释:
An object whose initializer is an empty set of parentheses, i.e., ()
, shall be value-initialized.
现在考虑 [dcl.init]/8(C++11 中的 /7†):
To value-initialize an object of type T
means:
- if
T
is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or a default constructor that is
user-provided or deleted, then the object is default-initialized;
- if
T
is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is
zero-initialized and the semantic constraints for
default-initialization are checked, and if T has a non-trivial default
constructor, the object is default-initialized;
- if
T
is an array type, then each element is value-initialized;
- otherwise, the object is zero-initialized.
因此 new A()
将对 m
进行零初始化。这应该等同于 A
和 B
.
new C
和 new C()
将再次默认初始化对象,因为最后引述的第一个要点适用(C 有一个用户提供的默认构造函数!) .但是,很明显,现在 m
在两种情况下都在构造函数中进行了初始化。
† 好吧,这一段在 C++11 中的措辞略有不同,但不会改变结果:
To value-initialize an object of type T
means:
- if
T
is a (possibly cv-qualified) class type (Clause 9) with a
user-provided constructor (12.1), then the default constructor for T
is called (and the initialization is ill-formed if T has no accessible
default constructor);
- if
T
is a (possibly cv-qualified) non-union
class type without a user-provided constructor, then the object is
zero-initialized and, if T
’s implicitly-declared default constructor
is non-trivial, that constructor is called.
- if
T
is an array type,
then each element is value-initialized;
- otherwise, the object is
zero-initialized.
以下答案扩展了答案,可作为 C++ 98 和 C++ 03 的参考
引用答案
- 在 C++1998 中有两种类型的初始化:零初始化和默认初始化
- 在 C++2003 中,第三种初始化类型是值初始化
已添加。
C++11(参考n3242)
初始化器
8.5 Initializers [dcl.init] 指定变量 POD 或非 POD 可以初始化为 brace-or-equal-initializer 可以是 braced-init-list 或 initializer-clause 统称为 brace-or-equal-initializer 或使用(表达式列表)。在 C++11 之前,尽管 initializer-clause[=] 仅支持 (expression-list) 或 initializer-clause 124=] 比我们在 C++11 中的限制更多。在 C++11 中,initializer-clause 除了 assignment-expression 现在还支持 braced-init-list ] 就像在 C++03 中一样。以下语法总结了新支持的子句,粗体部分为C++11标准新增。
初始值设定项:
    大括号或相等初始化器
    (表达式列表)
大括号或相等初始化器:
    = 初始化子句
    braced-init-list
初始化子句:
    assignment-表达式
    braced-init-list
初始化程序列表:
    initializer-clause ...opt
    初始化器列表,初始化器子句 ...opt**
支撑初始化列表:
    { 初始化列表 ,opt }
    { }
初始化
和C++03一样,C++11仍然支持三种初始化形式
备注
The part highlighted in bold has been added in C++11 and the one that is striked out has been removed from C++11.
初始化程序 Type:8.5.5 [dcl.init] _零初始化_
在以下情况下执行
- 具有静态或线程存储持续时间的对象被零初始化
- 如果初始化器的数量少于数组元素的数量,则每个未显式初始化的元素都应进行零初始化
- 在 value-initialize 期间,如果 T 是一个(可能是 cv 限定的)非联合 class 类型,没有用户提供的构造函数,则对象是零初始化。
To zero-initialize an object or reference of type T means:
- if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;
- if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
- if T is a (possibly cv-qualified) union type, the object’s first non-static named data member is zero initialized and padding is initialized to zero bits;
- if T is an array type, each element is zero-initialized;
- if T is a reference type, no initialization is performed.
2。初始化程序类型:8.5.6 [dcl.init] _default-initialize_
在以下情况下执行
- 如果省略new-initializer,对象默认初始化;如果未执行初始化,则对象具有不确定的值。
- 如果没有为对象指定初始化器,则对象默认初始化,具有静态或线程存储持续时间的对象除外
- 当基 class 或非静态数据成员未在构造函数初始化列表中提及且调用该构造函数时。
To default-initialize an object of type T means:
- if T is a (possibly cv-qualified)
non-POD class type (Clause 9), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
- if T is an array type, each element is default-initialized;
- otherwise, no initialization is performed.
Note Until C++11, only non-POD class types with automatic storage duration were considered to be default-initialized when no initializer is used.
3。初始化程序类型:8.5.7 [dcl.init] _value-initialize_
- 当一个对象(无名临时变量、命名变量、动态存储持续时间或非静态数据成员)的初始值设定项是一组空括号,即 () 或大括号 {}
To value-initialize an object of type T means:
- if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible
default constructor);
- if T is a (possibly cv-qualified) non-union class type without a user-provided constructor,
then every non-static data member and base-class component of T is value-initialized; then the object is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is called.
- if T is an array type, then each element is value-initialized;
- otherwise, the object is zero-initialized.
总结一下
Note The relevant quotation from the standard is highlighted in bold
- 新 A : 默认初始化(留下 A::m 未初始化)
- new A() : 零初始化 A,因为值初始化候选没有用户提供或删除的默认构造函数。 如果 T 是一个(可能是 cv 限定的)非联合 class 类型,没有用户提供的构造函数,那么对象是零初始化的,如果 T 是隐式声明的默认构造函数是非常重要的,调用该构造函数。
- 新 B : 默认初始化(留下 B::m 未初始化)
- new B() : 对 B 进行值初始化,对所有字段进行零初始化; 如果 T 是(可能是 cv 限定的)class 类型(第 9 条),具有用户提供的构造函数 (12.1),则调用 T 的默认构造函数
- new C : 默认初始化 C,调用默认构造函数。 如果 T 是(可能是 cv 限定的)class 类型(第 9 条),则调用 T 的默认构造函数,此外如果省略 new-initializer,对象默认初始化
- new C() : 值初始化 C,调用默认构造函数。 如果 T 是(可能是 cv 限定的)class 类型(第 9 条),具有用户提供的构造函数 (12.1),则调用 T 的默认构造函数。此外,初始化器为空括号集的对象,即(),应被值初始化
我可以确认,在 C++11 中,C++14 下的问题中提到的所有内容都是正确的,至少根据编译器实现是这样。
为了验证这一点,我在我的 test suite 中添加了以下代码。我在 GCC 7.4.0、GCC 5.4.0、Clang 10.0.1 和 VS 2017 中使用 -std=c++11 -O3
进行了测试,并且以下所有测试都通过了。
#include <gtest/gtest.h>
#include <memory>
struct A { int m; };
struct B { int m; ~B(){}; };
struct C { int m; C():m(){}; ~C(){}; };
struct D { int m; D(){}; };
struct E { int m; E() = default; };
struct F { int m; F(); }; F::F() = default;
// We use this macro to fill stack memory with something else than 0.
// Subsequent calls to EXPECT_NE(a.m, 0) are undefined behavior in theory, but
// pass in practice, and help illustrate that `a.m` is indeed not initialized
// to zero. Note that we initially tried the more aggressive test
// EXPECT_EQ(a.m, 42), but it didn't pass on all compilers (a.m wasn't equal to
// 42, but was still equal to some garbage value, not zero).
//
// Update 2020-12-14: Even the less aggressive EXPECT_NE(a.m, 0) fails in some
// machines, so we comment them out. But this change in behavior does illustrate
// that, in fact, the behavior was undefined.
//
#define FILL { int m = 42; EXPECT_EQ(m, 42); }
// We use this macro to fill heap memory with something else than 0, before
// doing a placement new at that same exact location. Subsequent calls to
// EXPECT_EQ(a->m, 42) are undefined behavior in theory, but pass in practice,
// and help illustrate that `a->m` is indeed not initialized to zero.
//
#define FILLH(b) std::unique_ptr<int> bp(new int(42)); int* b = bp.get(); EXPECT_EQ(*b, 42)
TEST(TestZero, StackDefaultInitialization)
{
//{ FILL; A a; EXPECT_NE(a.m, 0); } // UB!
//{ FILL; B a; EXPECT_NE(a.m, 0); } // UB!
{ FILL; C a; EXPECT_EQ(a.m, 0); }
//{ FILL; D a; EXPECT_NE(a.m, 0); } // UB!
//{ FILL; E a; EXPECT_NE(a.m, 0); } // UB!
//{ FILL; F a; EXPECT_NE(a.m, 0); } // UB!
}
TEST(TestZero, StackValueInitialization)
{
{ FILL; A a = A(); EXPECT_EQ(a.m, 0); }
{ FILL; B a = B(); EXPECT_EQ(a.m, 0); }
{ FILL; C a = C(); EXPECT_EQ(a.m, 0); }
//{ FILL; D a = D(); EXPECT_NE(a.m, 0); } // UB!
{ FILL; E a = E(); EXPECT_EQ(a.m, 0); }
//{ FILL; F a = F(); EXPECT_NE(a.m, 0); } // UB!
}
TEST(TestZero, StackListInitialization)
{
{ FILL; A a{}; EXPECT_EQ(a.m, 0); }
{ FILL; B a{}; EXPECT_EQ(a.m, 0); }
{ FILL; C a{}; EXPECT_EQ(a.m, 0); }
//{ FILL; D a{}; EXPECT_NE(a.m, 0); } // UB!
{ FILL; E a{}; EXPECT_EQ(a.m, 0); }
//{ FILL; F a{}; EXPECT_NE(a.m, 0); } // UB!
}
TEST(TestZero, HeapDefaultInitialization)
{
{ FILLH(b); A* a = new (b) A; EXPECT_EQ(a->m, 42); } // ~UB
{ FILLH(b); B* a = new (b) B; EXPECT_EQ(a->m, 42); } // ~UB
{ FILLH(b); C* a = new (b) C; EXPECT_EQ(a->m, 0); }
{ FILLH(b); D* a = new (b) D; EXPECT_EQ(a->m, 42); } // ~UB
{ FILLH(b); E* a = new (b) E; EXPECT_EQ(a->m, 42); } // ~UB
{ FILLH(b); F* a = new (b) F; EXPECT_EQ(a->m, 42); } // ~UB
}
TEST(TestZero, HeapValueInitialization)
{
{ FILLH(b); A* a = new (b) A(); EXPECT_EQ(a->m, 0); }
{ FILLH(b); B* a = new (b) B(); EXPECT_EQ(a->m, 0); }
{ FILLH(b); C* a = new (b) C(); EXPECT_EQ(a->m, 0); }
{ FILLH(b); D* a = new (b) D(); EXPECT_EQ(a->m, 42); } // ~UB
{ FILLH(b); E* a = new (b) E(); EXPECT_EQ(a->m, 0); }
{ FILLH(b); F* a = new (b) F(); EXPECT_EQ(a->m, 42); } // ~UB
}
TEST(TestZero, HeapListInitialization)
{
{ FILLH(b); A* a = new (b) A{}; EXPECT_EQ(a->m, 0); }
{ FILLH(b); B* a = new (b) B{}; EXPECT_EQ(a->m, 0); }
{ FILLH(b); C* a = new (b) C{}; EXPECT_EQ(a->m, 0); }
{ FILLH(b); D* a = new (b) D{}; EXPECT_EQ(a->m, 42); } // ~UB
{ FILLH(b); E* a = new (b) E{}; EXPECT_EQ(a->m, 0); }
{ FILLH(b); F* a = new (b) F{}; EXPECT_EQ(a->m, 42); } // ~UB
}
int main(int argc, char **argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
提到UB!
的地方是未定义的行为,实际行为可能取决于很多因素(a.m
可能等于42、0或其他垃圾)。提到 ~UB
的地方在理论上也是未定义的行为,但在实践中,由于使用了新的放置, a->m
不太可能等于 42.
我对值初始化、默认初始化和零初始化感到很困惑。 尤其是当他们针对不同的标准 C++03 和 C++11 (以及 C++14 ).
我在这里引用并尝试扩展一个非常好的答案 Value-/Default-/Zero- Init C++98 and C++03 以使其更通用,因为如果有人可以帮助填补所需的空白以更好地概述什么时候会发生什么?
通过示例概括的完整见解:
有时 new 运算符返回的内存会被初始化,有时不会,这取决于您要更新的类型是 POD (plain old data),还是 class包含 POD 成员并使用编译器生成的默认构造函数。
- 在C++1998中有两种类型的初始化:zero-和default-initialization
- 在C++2003第三种初始化类型中,添加了值初始化。
- 在 C++2011/C++2014 中仅添加了 list-initialization 和 的规则value-/default-/zero-initialization有点变了。
假设:
struct A { int m; };
struct B { ~B(); int m; };
struct C { C() : m(){}; ~C(); int m; };
struct D { D(){}; int m; };
struct E { E() = default; int m;}; /** only possible in c++11/14 */
struct F {F(); int m;}; F::F() = default; /** only possible in c++11/14 */
在 C++98 编译器中,应出现以下情况:
new A
- 不确定值(A
是 POD)new A()
- 零初始化new B
- 默认构造(B::m
未初始化,B
是非 POD)new B()
- 默认构造(B::m
未初始化)new C
- 默认构造(C::m
为零初始化,C
为非 POD)new C()
- 默认构造(C::m
为零初始化)new D
- 默认构造(D::m
未初始化,D
是非 POD)new D()
- 默认构造?(D::m
未初始化)
在符合 C++03 的编译器中,事情应该像这样工作:
new A
- 不确定值(A
是 POD)new A()
- 值初始化A
,这是零初始化,因为它是 POD。new B
- 默认初始化(B::m
未初始化,B
是非 POD)new B()
- 值初始化B
零初始化所有字段,因为它的默认 ctor 是编译器生成的而不是用户定义的。new C
- 默认初始化C
,调用默认构造函数。 (C::m
为零初始化,C
为非 POD)new C()
- 值初始化C
,调用默认构造函数。 (C::m
为零初始化)new D
- 默认构造(D::m
未初始化,D
是非 POD)new D()
- value-initializes D?,调用默认的构造函数(D::m
未初始化)
斜体值和 ?有不明之处,请帮忙改正:-)
在符合 C++11 的编译器中,事情应该像这样工作:
??? (如果我从这里开始,请帮助它无论如何都会出错)
在符合 C++14 的编译器中,事情应该像这样工作: ??? (如果我从这里开始,请帮助它无论如何都会出错) (根据答案草稿)
new A
- 默认初始化A
,编译器生成。 ctor, (leavsA::m
未初始化) (A
is POD)new A()
- 值初始化A
,这是零初始化,因为 2. 指向 [dcl.init]/8new B
- 默认初始化B
,编译器生成。 ctor, (leavsB::m
未初始化) (B
是非 POD)new B()
- 值初始化B
零初始化所有字段,因为它的默认 ctor 是编译器生成的而不是用户定义的。new C
- 默认初始化C
,调用默认 ctor。 (C::m
为零初始化,C
为非 POD)new C()
- 值初始化C
,调用默认构造函数。 (C::m
为零初始化)new D
- 默认初始化D
(D::m
未初始化,D
非 POD)new D()
- 值初始化D
,调用默认 ctor(D::m
未初始化)new E
- 默认初始化E
,调用 comp。将军演员。 (E::m
未初始化,E为非POD)new E()
- 值初始化E
,从 [dcl.init] 中的 2 点开始零初始化E
/8 )new F
- 默认初始化F
,调用 comp。将军演员。 (F::m
未初始化,F
非 POD)new F()
- 值初始化F
,默认初始化F
,因为 1. 指向 [dcl.init]/8 (F
ctor 函数是用户提供的,如果它是用户声明的并且在第一次声明时没有明确默认或删除。Link)
C++14 指定在 [expr.new]/17 中使用 new
创建的对象的初始化(在 C++11 中为 [expr.new]/15,注释为当时不是注释而是规范文本):
A new-expression that creates an object of type
T
initializes that object as follows:
- If the new-initializer is omitted, the object is default-initialized (8.5). [ Note: If no initialization is performed, the object has an indeterminate value. — end note ]
- Otherwise, the new-initializer is interpreted according to the initialization rules of 8.5 for direct-initialization.
default-initialization定义在[dcl.init]/7(C++11中的/6,写法本身也有同样的作用):
To default-initialize an object of type
T
means:
- if
T
is a (possibly cv-qualified) class type (Clause 9), the default constructor (12.1) forT
is called (and the initialization is ill-formed ifT
has no default constructor or overload resolution (13.3) results in an ambiguity or in a function that is deleted or inaccessible from the context of the initialization);- if
T
is an array type, each element is default-initialized;- otherwise, no initialization is performed.
因此
new A
只会导致调用A
的默认构造函数,而不会初始化m
。不确定的价值。new B
. 应该相同
new A()
根据 [dcl.init]/11(C++11 中的 /10)解释:An object whose initializer is an empty set of parentheses, i.e.,
()
, shall be value-initialized.现在考虑 [dcl.init]/8(C++11 中的 /7†):
To value-initialize an object of type
T
means:- if
T
is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or a default constructor that is user-provided or deleted, then the object is default-initialized; - if
T
is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized; - if
T
is an array type, then each element is value-initialized; - otherwise, the object is zero-initialized.
因此
new A()
将对m
进行零初始化。这应该等同于A
和B
.- if
new C
和new C()
将再次默认初始化对象,因为最后引述的第一个要点适用(C 有一个用户提供的默认构造函数!) .但是,很明显,现在m
在两种情况下都在构造函数中进行了初始化。
† 好吧,这一段在 C++11 中的措辞略有不同,但不会改变结果:
To value-initialize an object of type
T
means:
- if
T
is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor forT
is called (and the initialization is ill-formed if T has no accessible default constructor);- if
T
is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, ifT
’s implicitly-declared default constructor is non-trivial, that constructor is called.- if
T
is an array type, then each element is value-initialized;- otherwise, the object is zero-initialized.
以下答案扩展了答案,可作为 C++ 98 和 C++ 03 的参考
引用答案
- 在 C++1998 中有两种类型的初始化:零初始化和默认初始化
- 在 C++2003 中,第三种初始化类型是值初始化 已添加。
C++11(参考n3242)
初始化器
8.5 Initializers [dcl.init] 指定变量 POD 或非 POD 可以初始化为 brace-or-equal-initializer 可以是 braced-init-list 或 initializer-clause 统称为 brace-or-equal-initializer 或使用(表达式列表)。在 C++11 之前,尽管 initializer-clause[=] 仅支持 (expression-list) 或 initializer-clause 124=] 比我们在 C++11 中的限制更多。在 C++11 中,initializer-clause 除了 assignment-expression 现在还支持 braced-init-list ] 就像在 C++03 中一样。以下语法总结了新支持的子句,粗体部分为C++11标准新增。
初始值设定项:
    大括号或相等初始化器
    (表达式列表)
大括号或相等初始化器:
    = 初始化子句
    braced-init-list
初始化子句:
    assignment-表达式
    braced-init-list
初始化程序列表:
    initializer-clause ...opt
    初始化器列表,初始化器子句 ...opt**
支撑初始化列表:
    { 初始化列表 ,opt }
    { }
初始化
和C++03一样,C++11仍然支持三种初始化形式
备注
The part highlighted in bold has been added in C++11 and the one that is striked out has been removed from C++11.
初始化程序 Type:8.5.5 [dcl.init] _零初始化_
在以下情况下执行
- 具有静态或线程存储持续时间的对象被零初始化
- 如果初始化器的数量少于数组元素的数量,则每个未显式初始化的元素都应进行零初始化
- 在 value-initialize 期间,如果 T 是一个(可能是 cv 限定的)非联合 class 类型,没有用户提供的构造函数,则对象是零初始化。
To zero-initialize an object or reference of type T means:
- if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;
- if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
- if T is a (possibly cv-qualified) union type, the object’s first non-static named data member is zero initialized and padding is initialized to zero bits;
- if T is an array type, each element is zero-initialized;
- if T is a reference type, no initialization is performed.
2。初始化程序类型:8.5.6 [dcl.init] _default-initialize_
在以下情况下执行
- 如果省略new-initializer,对象默认初始化;如果未执行初始化,则对象具有不确定的值。
- 如果没有为对象指定初始化器,则对象默认初始化,具有静态或线程存储持续时间的对象除外
- 当基 class 或非静态数据成员未在构造函数初始化列表中提及且调用该构造函数时。
To default-initialize an object of type T means:
- if T is a (possibly cv-qualified)
non-PODclass type (Clause 9), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);- if T is an array type, each element is default-initialized;
- otherwise, no initialization is performed.
Note Until C++11, only non-POD class types with automatic storage duration were considered to be default-initialized when no initializer is used.
3。初始化程序类型:8.5.7 [dcl.init] _value-initialize_
- 当一个对象(无名临时变量、命名变量、动态存储持续时间或非静态数据成员)的初始值设定项是一组空括号,即 () 或大括号 {}
To value-initialize an object of type T means:
- if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
- if T is a (possibly cv-qualified) non-union class type without a user-provided constructor,
then every non-static data member and base-class component of T is value-initialized;then the object is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is called.- if T is an array type, then each element is value-initialized;
- otherwise, the object is zero-initialized.
总结一下
Note The relevant quotation from the standard is highlighted in bold
- 新 A : 默认初始化(留下 A::m 未初始化)
- new A() : 零初始化 A,因为值初始化候选没有用户提供或删除的默认构造函数。 如果 T 是一个(可能是 cv 限定的)非联合 class 类型,没有用户提供的构造函数,那么对象是零初始化的,如果 T 是隐式声明的默认构造函数是非常重要的,调用该构造函数。
- 新 B : 默认初始化(留下 B::m 未初始化)
- new B() : 对 B 进行值初始化,对所有字段进行零初始化; 如果 T 是(可能是 cv 限定的)class 类型(第 9 条),具有用户提供的构造函数 (12.1),则调用 T 的默认构造函数
- new C : 默认初始化 C,调用默认构造函数。 如果 T 是(可能是 cv 限定的)class 类型(第 9 条),则调用 T 的默认构造函数,此外如果省略 new-initializer,对象默认初始化
- new C() : 值初始化 C,调用默认构造函数。 如果 T 是(可能是 cv 限定的)class 类型(第 9 条),具有用户提供的构造函数 (12.1),则调用 T 的默认构造函数。此外,初始化器为空括号集的对象,即(),应被值初始化
我可以确认,在 C++11 中,C++14 下的问题中提到的所有内容都是正确的,至少根据编译器实现是这样。
为了验证这一点,我在我的 test suite 中添加了以下代码。我在 GCC 7.4.0、GCC 5.4.0、Clang 10.0.1 和 VS 2017 中使用 -std=c++11 -O3
进行了测试,并且以下所有测试都通过了。
#include <gtest/gtest.h>
#include <memory>
struct A { int m; };
struct B { int m; ~B(){}; };
struct C { int m; C():m(){}; ~C(){}; };
struct D { int m; D(){}; };
struct E { int m; E() = default; };
struct F { int m; F(); }; F::F() = default;
// We use this macro to fill stack memory with something else than 0.
// Subsequent calls to EXPECT_NE(a.m, 0) are undefined behavior in theory, but
// pass in practice, and help illustrate that `a.m` is indeed not initialized
// to zero. Note that we initially tried the more aggressive test
// EXPECT_EQ(a.m, 42), but it didn't pass on all compilers (a.m wasn't equal to
// 42, but was still equal to some garbage value, not zero).
//
// Update 2020-12-14: Even the less aggressive EXPECT_NE(a.m, 0) fails in some
// machines, so we comment them out. But this change in behavior does illustrate
// that, in fact, the behavior was undefined.
//
#define FILL { int m = 42; EXPECT_EQ(m, 42); }
// We use this macro to fill heap memory with something else than 0, before
// doing a placement new at that same exact location. Subsequent calls to
// EXPECT_EQ(a->m, 42) are undefined behavior in theory, but pass in practice,
// and help illustrate that `a->m` is indeed not initialized to zero.
//
#define FILLH(b) std::unique_ptr<int> bp(new int(42)); int* b = bp.get(); EXPECT_EQ(*b, 42)
TEST(TestZero, StackDefaultInitialization)
{
//{ FILL; A a; EXPECT_NE(a.m, 0); } // UB!
//{ FILL; B a; EXPECT_NE(a.m, 0); } // UB!
{ FILL; C a; EXPECT_EQ(a.m, 0); }
//{ FILL; D a; EXPECT_NE(a.m, 0); } // UB!
//{ FILL; E a; EXPECT_NE(a.m, 0); } // UB!
//{ FILL; F a; EXPECT_NE(a.m, 0); } // UB!
}
TEST(TestZero, StackValueInitialization)
{
{ FILL; A a = A(); EXPECT_EQ(a.m, 0); }
{ FILL; B a = B(); EXPECT_EQ(a.m, 0); }
{ FILL; C a = C(); EXPECT_EQ(a.m, 0); }
//{ FILL; D a = D(); EXPECT_NE(a.m, 0); } // UB!
{ FILL; E a = E(); EXPECT_EQ(a.m, 0); }
//{ FILL; F a = F(); EXPECT_NE(a.m, 0); } // UB!
}
TEST(TestZero, StackListInitialization)
{
{ FILL; A a{}; EXPECT_EQ(a.m, 0); }
{ FILL; B a{}; EXPECT_EQ(a.m, 0); }
{ FILL; C a{}; EXPECT_EQ(a.m, 0); }
//{ FILL; D a{}; EXPECT_NE(a.m, 0); } // UB!
{ FILL; E a{}; EXPECT_EQ(a.m, 0); }
//{ FILL; F a{}; EXPECT_NE(a.m, 0); } // UB!
}
TEST(TestZero, HeapDefaultInitialization)
{
{ FILLH(b); A* a = new (b) A; EXPECT_EQ(a->m, 42); } // ~UB
{ FILLH(b); B* a = new (b) B; EXPECT_EQ(a->m, 42); } // ~UB
{ FILLH(b); C* a = new (b) C; EXPECT_EQ(a->m, 0); }
{ FILLH(b); D* a = new (b) D; EXPECT_EQ(a->m, 42); } // ~UB
{ FILLH(b); E* a = new (b) E; EXPECT_EQ(a->m, 42); } // ~UB
{ FILLH(b); F* a = new (b) F; EXPECT_EQ(a->m, 42); } // ~UB
}
TEST(TestZero, HeapValueInitialization)
{
{ FILLH(b); A* a = new (b) A(); EXPECT_EQ(a->m, 0); }
{ FILLH(b); B* a = new (b) B(); EXPECT_EQ(a->m, 0); }
{ FILLH(b); C* a = new (b) C(); EXPECT_EQ(a->m, 0); }
{ FILLH(b); D* a = new (b) D(); EXPECT_EQ(a->m, 42); } // ~UB
{ FILLH(b); E* a = new (b) E(); EXPECT_EQ(a->m, 0); }
{ FILLH(b); F* a = new (b) F(); EXPECT_EQ(a->m, 42); } // ~UB
}
TEST(TestZero, HeapListInitialization)
{
{ FILLH(b); A* a = new (b) A{}; EXPECT_EQ(a->m, 0); }
{ FILLH(b); B* a = new (b) B{}; EXPECT_EQ(a->m, 0); }
{ FILLH(b); C* a = new (b) C{}; EXPECT_EQ(a->m, 0); }
{ FILLH(b); D* a = new (b) D{}; EXPECT_EQ(a->m, 42); } // ~UB
{ FILLH(b); E* a = new (b) E{}; EXPECT_EQ(a->m, 0); }
{ FILLH(b); F* a = new (b) F{}; EXPECT_EQ(a->m, 42); } // ~UB
}
int main(int argc, char **argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
提到UB!
的地方是未定义的行为,实际行为可能取决于很多因素(a.m
可能等于42、0或其他垃圾)。提到 ~UB
的地方在理论上也是未定义的行为,但在实践中,由于使用了新的放置, a->m
不太可能等于 42.