默认值,零初始化乱七八糟
我对值初始化和默认初始化非常困惑。 特别是当他们开始使用不同的标准C ++ 03和C ++ 11 (和C ++ 14 )时。
我引用并试图扩展一个非常好的答案Value- / Default- / Zero-Init C ++ 98和C ++ 03在这里使它更通用,因为如果有人可以帮助填写需要的差距有一个很好的概述什么时候发生?
简而言之,通过实例的全面洞察力:
有时,新运算符返回的内存将被初始化,有时它不会取决于新build的types是POD(普通旧数据) ,还是包含POD成员的类,并且正在使用编译器生成的默认构造函数。
- 在C ++ 1998中有两种types的初始化:零和默认
- 在C ++ 2003中第三种types的初始化,增加了值初始化。
- 在C ++ 2011 / C ++ 2014中,只添加了列表初始化,并且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编译器中,应该发生以下情况 :
- 新的A – 不确定值(A是POD)
- 新的A() – 零初始化
- 新的B – 默认构造(B :: m是未初始化的,B是非POD)
- 新的B() – 默认构造(B :: m未初始化)
- 新的C – 默认构造(C :: m是零初始化的,C是非POD)
- 新的C() – 默认构造(C :: m是零初始化的)
- 新的D – 默认构造(D :: m是未初始化的,D是非POD)
- 新的D() – 默认构造? (D :: m未初始化)
在符合C ++ 03的编译器中,事情应该像这样工作:
- 新的A – 不确定值(A是POD)
- 新的A() – 值初始化A,这是零初始化,因为它是一个POD。
- 新的B – 默认初始化(B :: m未初始化,B是非POD)
- 新的B() – 值初始化B零初始化所有领域,因为它的默认ctor是编译器生成而不是用户定义。
- 新C – 默认初始化C,它调用默认的ctor。 (C :: m是零初始化的,C是非POD)
- 新的C() – 值 – 初始化C,它调用默认的ctor。 (C :: m是零初始化的)
- 新的D – 默认构造(D :: m是未初始化的,D是非POD)
- 新的D() – 初始化D? ,它调用默认的ctor(D :: m是未初始化的)
斜体值和? 是不确定的,请帮助解决这个问题:-)
在一个C ++ 11兼容的编译器中,事情应该像这样工作:
??? (请帮助,如果我从这里开始反正会出错)
在一个C ++ 14兼容的编译器中,事情应该像这样工作: (请帮助,如果我从这里开始将无论如何出错) (根据答案草案)
- 新的A – 默认初始化A,编译器gen。 (未初始化的A :: m)(A是POD)
-
新的A() – 值初始化A,这是从2开始的零初始化[dcl.init] / 8
-
新的B – 默认初始化B,编译器gen。 (未初始化的A :: m)(B是非POD)
- 新的B() – 值初始化B零初始化所有领域,因为它的默认ctor是编译器生成而不是用户定义。
- 新C – 默认初始化C,它调用默认的ctor。 (C :: m是零初始化的,C是非POD)
- 新的C() – 值 – 初始化C,它调用默认的ctor。 (C :: m是零初始化的)
- 新的D – 默认初始化D(D :: m是未初始化的,D是非POD)
- 新的D() – 值初始化D,它调用默认的ctor(D :: m是未初始化的)
- 新E – 默认初始化E,它调用comp。 根。 构造函数。 (D :: m是未初始化的,D是非POD)
- 新的E() – 初始化E,它初始化E,因为2 [dcl.init] / 8 )
- 新F – 默认初始化F,它调用comp。 根。 构造函数。 (D :: m是未初始化的,D是非POD)
- 新的F() – 值初始化F,它默认初始化 F,因为1.指向[dcl.init] / 8 (函数是用户提供的,如果它是用户声明的,并且不在第一个声明中显式默认或删除。 链接 )
C ++ 14指定了在[expr.new] / 17(C ++ 11中的[expr.new] / 15中用new
创build的对象的初始化,并且该注释不是注释,而是当时的规范文本):
创buildtypes为
T
的对象的新expression式按如下方式初始化该对象:
- 如果省略了new-initializer ,则该对象将被默认初始化 (8.5)。 [ 注意:如果没有执行初始化,则该对象具有不确定的值。 – 结束注意 ]
- 否则,按照8.5的初始化规则对新的初始化器进行解释以进行直接初始化 。
默认初始化是在[dcl.init] / 7(/ 6在C ++ 11中定义的,其措辞本身具有相同的效果)中定义:
默认初始化
T
types的对象意味着:
- 如果
T
是(可能是cv-qualified)类types(第9章),则调用T
的默认构造函数(12.1)(如果T
没有默认构造函数或重载parsing(13.3)模糊性或从初始化上下文中删除或不可访问的函数中);- 如果
T
是数组types,则每个元素都是默认初始化的 ;- 否则,不执行初始化。
从而
-
new A
唯一原因默认的构造函数被调用,它不会初始化m
。 不确定的价值。 对于new B
应该是一样的。 -
new A()
根据[dcl.init] / 11(C ++ 11中的/ 10)解释:对象的初始化方法是一组空括号,即
()
,应进行值初始化。现在考虑[dcl.init] / 8(/ 7在C ++ 11†):
为了初始化
T
types的对象,意味着:- 如果
T
是没有默认构造函数(12.1)或用户提供或删除的默认构造函数(可能是cv-qualified)的类types(第9章),则该对象被默认初始化; - 如果
T
是没有用户提供或删除的默认构造函数的(可能是cv-qualified)类types,那么该对象是零初始化的,并且检查用于缺省初始化的语义约束,并且如果T具有非平凡的默认构造函数,对象被默认初始化; - 如果
T
是一个数组types,那么每个元素都是值初始化的; - 否则,该对象是零初始化的。
因此
new A()
将会初始化m
。 这应该相当于A
和B
- 如果
-
new C
和new C()
将默认初始化对象,因为从最后一个引用的第一个项目符号适用(C有一个用户提供的默认构造!)。 但是,显然,在这两种情况下,现在m
在构造函数中被初始化。
†嗯,这段在C ++ 11中的措辞略有不同,它不会改变结果:
为了初始化
T
types的对象,意味着:
- 如果
T
是用户提供的构造函数(12.1)(可能是cv-qualified)的类types(第9章),则调用T
的默认构造函数(如果T没有可访问的默认构造函数, ;- 如果
T
是没有用户提供的构造函数的(可能是cv限定的)非联合类types,则该对象是零初始化的,并且如果T
隐含声明的默认构造函数是非平凡的,则调用该构造函数。- 如果
T
是一个数组types,那么每个元素都是值初始化的;- 否则,该对象是零初始化的。
下面的答案扩展了答案https://stackoverflow.com/a/620402/977038这将作为参考C + + 98和C + + 03
引用答案
- 在C ++ 1998中有两种types的初始化:零和默认
- 在C ++ 2003中第三种types的初始化,增加了值初始化。
C ++ 11(参考n3242)
初始化器
8.5初始化器[dcl.init]指定variablesPOD或非POD可以初始化为括号或等于初始值设定项 ,它可以是braced-init-list或初始化子句总称为括号或等同初始值设定项 , 初始值设定项或使用(expression式列表) 。 在C ++ 11之前,只有(expression-list)或initializer-clause被支持,尽pipe初始化子句比C ++ 11更受限制。 在C ++ 11中, 初始化子句现在支持与C ++ 03中的赋值expression式不同的braced-init-list 。 以下语法总结了新的支持子句,其中部分为粗体的部分是在C ++ 11标准中新添加的。
初始化:
大括号或相等的初始值设定
(expression列表)
大括号或相等的初始值设定:
=初始化子句
支撑-初始化列表
初始化子句:
&nbspassignmentexpression
支撑-初始化列表
初始化列表:
初始化子句… opt
初始化器列表,初始化器子句… opt **
支撑-初始化列表:
{初始化列表,select}
{}
初始化
像C ++ 03一样,C ++ 11仍然支持三种forms的初始化
注意
粗体突出显示的部分已经添加到C ++ 11中,被删除的部分已经从C ++ 11中删除。
-
初始化程序types:8.5.5 [dcl.init] _zero-initialize_
在以下情况下执行
- 具有静态或线程存储持续时间的对象是零初始化的
- 如果初始值设定项比数组元素less,则每个未明确初始化的元素都应该被初始化
- 在值初始化期间,如果T是没有用户提供的构造函数的(可能是cv合格的)非联合类types,则该对象是零初始化的。
要初始化Ttypes的对象或引用意味着:
- 如果T是标量types(3.9),则将该对象设置为值0(零), 将其作为整数常量expression式转换为T;
- 如果T是一个(可能是cv合格的)非联合类types,那么每个非静态数据成员和每个基类子对象都是零初始化的, 并且填充被初始化为0位;
- 如果T是(可能是cv限定的)联合types,则该对象的第一个非静态命名数据成员是零初始化的, 并且填充初始化为零位;
- 如果T是一个数组types,则每个元素都是零初始化的;
- 如果T是引用types,则不执行初始化。
2.初始化程序types:8.5.6 [dcl.init] _default-initialize_
在以下情况下执行
- 如果省略了new-initializer,则该对象将被默认初始化; 如果没有执行初始化,则该对象具有不确定的值。
- 如果没有为对象指定初始化程序,则对象将被默认初始化,除了具有静态或线程存储持续时间的对象
- 在构造函数初始值设定项列表中未提及基类或非静态数据成员,并调用该构造函数时。
默认初始化Ttypes的对象意味着:
- 如果T是一个(可能是cv合格的)
非POD类types(第9章),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化不合格)。- 如果T是数组types,则每个元素都是默认初始化的;
- 否则,不执行初始化。
注意在C ++ 11之前,只有没有使用初始化程序的情况下,只有具有自动存储持续时间的非POD类types被认为是默认初始化的。
3.初始化程序types:8.5.7 [dcl.init] _value-initialize_
- 当一个对象(无名临时variables,命名variables,dynamic存储持续时间或非静态数据成员)的初始值设定项是一组空括号时,即()或大括号{}
为了初始化Ttypes的对象,意味着:
- 如果T是用户提供的构造函数(12.1)的一个(可能是cv-qualified)类types(第9章),那么调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化不合格) ;
- 如果T是一个没有用户提供的构造函数的(可能是cv限定的)非联合类types,
那么T中的每个非静态数据成员和基类组件都是值初始化的;那么该对象是零初始化的,如果T的隐式声明的默认构造函数是非平凡的,则调用该构造函数。- 如果T是一个数组types,那么每个元素都是值初始化的;
- 否则,该对象是零初始化的。
所以总结一下
注意标准中的相关引用以粗体突出显示
- 新的答:默认初始化(叶A :: m未初始化)
- 新的A():零初始化A,作为值初始化的候选人没有用户提供或删除的默认构造函数。 如果T是一个没有用户提供的构造函数的(可能是cv限定的)非联合类types,那么该对象是零初始化的,如果T隐含声明的默认构造函数是非平凡的,则调用该构造函数。
- 新的B:默认初始化(离开B :: m未初始化)
- 新的B():值初始化B零初始化所有字段; 如果T是用户提供的构造函数(12.1)的一个(可能是cv-qualified)类types(第9章),那么T的默认构造函数被调用
- 新的C:默认 – 初始化C,它调用默认的ctor。 如果T是(可能是cv-qualified)类types(第9章),则调用T的默认构造函数 ,此外,如果省略了new-initializer,则该对象将被默认初始化
- 新的C():value-初始化C,它调用默认的ctor。 如果T是用户提供的构造函数(12.1)(可能是cv-qualified)类types(第9章),则调用T的默认构造函数。 而且, 一个对象的初始化方法是一组空括号,即(),应该进行值初始化