我对价值感到非常困惑 - &默认 - &零初始化。 特别是当他们为不同的标准 C ++ 03 和 C ++ 11 (以及 C ++ 14 )提供服务时。
我引用并试图在这里扩展一个非常好的答案Value-/Default-/Zero- Init C++98 and C++03以使其更加通用,因为它可以帮助很多用户,如果有人可以帮助填写所需的差距,以便对发生的事情有一个很好的概述当?
简要说明一下示例的完整见解:
有时新操作员返回的内存会被初始化,有时候根据您正在新建的类型是POD (plain old data)还是#{3}},或者它是否为“sa”来实现。包含POD成员并使用编译器生成的默认构造函数的类。
假设:
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()
- value-initialize A
,这是零初始化,因为它是POD。new B
- 默认初始化(离开B::m
未初始化,B
为非POD)new B()
- value-initializes B
,它对所有字段进行零初始化,因为它的默认ctor是编译器生成的而不是用户定义的。new C
- 默认初始化C
,调用默认的ctor。 (C::m
为零初始化,C
为非POD)new C()
- value-initializes C
,调用默认的ctor。 (C::m
为零初始化)new D
- 默认构造(D::m
未初始化,D
为非POD)new D()
- value-initializes D?,调用默认的ctor(D::m
未初始化)斜体值和?是不确定的,请帮助纠正这个: - )
在符合C ++ 11标准的编译器中,事情应该是这样的:
??? (如果我从这里开始,请帮助,无论如何都会出错)
在符合C ++ 14的编译器中,事情应该如下: ??? (如果我从这里开始请帮忙,无论如何都会出错) (基于答案的草案)
new A
- 默认初始化A
,编译器gen。 ctor,(leavs A::m
未初始化)(A
是POD) new A()
- value-initializes A
,这是从 [dcl.init] / 8
new B
- 默认初始化B
,编译器gen。 ctor,(leavs B::m
未初始化)(B
是非POD)
new B()
- value-initializes B
,它对所有字段进行零初始化,因为它的默认ctor是编译器生成的而不是用户定义的。new C
- 默认初始化C
,调用默认的ctor。 (C::m
为零初始化,C
为非POD)new C()
- value-initializes C
,调用默认的ctor。 (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] / 8 中的2点开始归零E
new F
- 默认初始化F
,调用comp。根。构造函数。 (F::m
未初始化,F
为非POD)new F()
- 值初始化F
,默认初始化 F
,因为1.指向 [dcl.init] / 8 < / em>(F
ctor函数是用户提供的,如果它是用户声明的,并且在第一次声明时未明确默认或删除。Link)答案 0 :(得分:24)
C ++ 14指定在[expr.new] / 17(C ++ 11中的[expr.new] / 15)中使用new
创建的对象的初始化,并且该注释不是注意但是当时的规范性文本):
创建类型为
T
的对象的 new-expression 初始化它 对象如下:
- 如果省略 new-initializer ,则对象默认初始化(8.5)。 [注意:如果没有初始化 执行后,该对象具有不确定的值。 - 结束说明]
- 否则, new-initializer 将根据8.5的 direct-initialization 的初始化规则进行解释。
默认初始化在[dcl.init] / 7中定义(C ++ 11中为/ 6,并且措辞本身具有相同的效果):
默认初始化
T
类型的对象意味着:
- 如果
T
是(可能是cv限定的)类类型(第9条),则调用T
的默认构造函数(12.1)(并初始化) 如果T
没有默认构造函数或重载决策,则格式错误 (13.3)导致歧义或被删除的函数或 从初始化的上下文无法访问);- 如果
T
是数组类型,则每个元素都是默认初始化;- 否则,不执行初始化。
因此
new A
仅会导致调用A
的默认构造函数,而不会初始化m
。不确定的价值。 new B
。 new A()
根据[dcl.init] / 11(C ++ 11中的/ 10)进行解释:
一个对象,其初始化程序是一组空的括号,即
()
,应进行值初始化。
现在考虑[dcl.init] / 8(C ++ 11†中的/ 7):
value-initialize
T
类型的对象意味着:
- 如果
T
是一个(可能是cv限定的)类类型(第9条),没有默认构造函数(12.1)或者是默认构造函数 用户提供或删除,然后该对象被默认初始化;- 如果
T
是一个(可能是cv限定的)类类型而没有用户提供或删除的默认构造函数,那么对象是 零初始化和语义约束 检查默认初始化,如果T具有非平凡的默认值 构造函数,该对象是默认初始化的;- 如果
T
是数组类型,则每个元素都是值初始化的;- 否则,该对象为零初始化。
因此new A()
将零初始化m
。对于A
和B
。
new C
和new C()
将默认初始化对象,因为最后一个引用的第一个项目符号点适用(C具有用户提供的默认构造函数!)。但是,显然,现在m
在两种情况下都在构造函数中初始化。
†嗯,这一段在C ++ 11中的措辞略有不同,不会改变结果:
value-initialize
T
类型的对象意味着:
- 如果
T
是一个(可能是cv限定的)类类型(第9条),带有 用户提供的构造函数(12.1),然后是T
的默认构造函数 被调用(如果T无法访问,则初始化是错误的 默认构造函数);- 如果
T
是(可能是cv合格的)非工会 类型没有用户提供的构造函数,那么对象就是 零初始化,如果T
隐式声明的默认构造函数 是非平凡的,那个构造函数被调用。- 如果
T
是数组类型, 然后每个元素都进行了值初始化;- 否则,对象是 初始化为零。
答案 1 :(得分:0)
我可以确认,在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).
//
#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以外的其他值的可能性很小。