C++的各种初始化方式
2017/10/7 11:11:27

C++小实验测试:下面程序中main函数里a.a和b.b的输出值是多少? 
#include <iostream>  struct foo {    foo() = default;    int a; };  struct bar {    bar();    int b; };  bar::bar() = default;  int main() {    foo a{};    bar b{};      std::cout << a.a << '\t' << b.b; } 
答案是a.a是0,b.b是不确定值(不论你是gcc编译器,还是clang编译器,或者是微软的msvc++编译器)。为什么会这样?这是因为C++中的初始化已经开始畸形发展了。
 
接下来,我要探索一下为什么会这样。在我们知道原因之前,先给出一些初始化的概念:默认初始化,值初始化,零初始化。 
T global;        //T是我们的自定义类型,首先零初始化,然后默认初始化  void foo() {    T i;        //默认初始化    T j{};      //值初始化(C++11)    T k = T();  //值初始化    T l = T{};  //值初始化(C++11)    T m();      //函数声明      new T;      //默认初始化    new T();    //值初始化    new T{};    //值初始化(C++11) }  struct A {    T t;    A() : t()    //t将值初始化    {        //构造函数    } };  struct B {    T t;    B() : t{}    //t将值初始化(C++11)    {        //构造函数    } };  struct C {    T t;    C()          //t将默认初始化    {        //构造函数    } }; 
  上面这些不同形式的初始化方式有点复杂,我会对这些C++11的初始化做一下简化:  默认初始化:如果T是一个类,那么调用默认构造函数进行初始化;如果是一个数组,每个元素默认初始化,否则不进行初始化,其值未定义。至于合成的默认构造函数初始化数据成员的规则是:1.如果类数据成员存在类内初始值,则用该值初始化相应成员(c++11);2.否则,默认初始化数据成员。 值初始化:如果T是一个类,那么类的对象进行默认初始化(如果T类型的默认构造函数不是用户自定义的,默认初始化之前先进行零初始化);如果是一个数组,每个元素值初始化,否则进行零初始化。 零初始化:对于static或者thread_local变量将会在其他类型的初始化之前先初始化。如果T是算数、指针、枚举类型,将会初始化为0;如果是类类型,基类和数据成员会零初始化;如果是数组,数组元素也零初始化。 
看一下上面的例子,如果T是int类型,那么global和那些T类型的使用值初始化形式的变量都会初始化为0(因为int是内置类型,不是类类型,也不是数组,将会零初始化,又因为int是算术类型,如果进行零初始化,则初始值为0)而其他的默认初始化都是未定义值。
 
回到开头的例子,现在我们已经有了搞明白这个例子所必要的基础知识。造成结果不同的根本原因是:foo和bar被它们不同位置的默认构造函数所影响。
foo的构造函数在起初声明时是要求默认合成,而不是我们自定义提供的,因此它属于编译器合成的默认构造函数。而bar的构造函数则不同,它是在定义时被要求合成,因此它属于我们用户自定义的默认构造函数。
前面提到的关于值初始化的规则时,有说明到:如果T类型的默认构造函数不是用户自定义的,默认初始化之前先进行零初始化。因为foo的默认构造函数不是我们自定义的,是编译器合成的,所以在对foo类型的对象进行值初始化时,会先进行一次零初始化,然后再调用默认构造函数,这导致a.a的值被初始化为0,而bar的默认构造函数是用户自定义的,所以不会进行零初始化,而是直接调用默认构造函数,从而导致b.b的值是未初始化的,因此每次都是随机值。
这个陷阱迫使我们注意:如果你不想要你的默认构造函数是用户自定义的,那么必须在类的内部声明处使用"=default",而不是在类外部定义处使用。
对于类

下一页
返回列表
返回首页
©2017 Linux公社 - Linux系统门户网站 电脑版