条款04:确定对象被使用前已被初始化
Make sure that object are initialized befor they’re used.
What
C++的语法规定:
1. C++不保证初始化内置型对象。
2. 对于内置类型以外的任何其他东西,初始化责任落在构造函数(constructors)身上。C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。
3. C++有着十分固定的“成员初始化次序”。Base classes 更早于其derived classes 被初始化,而class的成员变量总是以其声明次序被初始化。详细:基类首先被初始化(如果存在的话,如果是多重继承,那先初始化虚继承,然后再依次初始化),然后就是依据声明的次序依次初始化,如果初始化列表中存在定义,则调用初始化列表中的方法进行初始化,如果没有在初始化列表中初始化,那对于基本类型就根据编译器而定,类对象则调用其默认构造函数进行初始化(如果存在的话,如果不存在就必须在初始化列表中初始化)。
4. 成员变量是 const 或 references,它们就一定需要初值,不能被赋值。
5. C++对于“定义跨编译单元内的non-local static对象”的初始化相对次序并无明确定义。
6. C++中的static对象是指存储区不属于stack和heap、"寿命"从被构造出来直至程序结束为止的对象。这些对象包括全局对象,定义于namespace作用域的对象,在class、function以及file作用域中被声明为static的对象。其中,函数内的static对象称为local static 对象,而其它static对象称为non-local static对象。
7. 对于local static对象,在其所属的函数被调用之前,该对象并不存在,即只有在第一次调用对应函数时,local static对象才被构造出来。而对于non-local static对象,在main()函数开始前就已经被构造出来,并在main()函数结束后被析构,例子祥见最后一页代码。
Why
Ø 读取未初始化的值会导致不明确的行为。在某些平台上,仅仅只是读取未初始化的值,就可能让你的程序停止与你运行。更可能的情况是读入一些“伴随机”bits,污染了正在进行读取动作的那个对象,最终导致不可测知的程序行为,以及许多令人不许快的调试过程。
Ø 由于进入构造函数本体之后并非初始化,因此构造函数体内的对成员变量的赋值,实际上是伪初始化(pseudo-initialization),伪初始化效率低下:如果没有提供初始化列表,那么进入构造函数体之前,各成员变量已经调用默认构造函数初始化(自定义类型)或由编译器决定(内置类型对象的初始化),函数体内再赋值。
Ø 初始化列表中:成员出现的次序即使不是声明的次序,也不会改变初始化默认的初始化顺序:按声明次序初始化class member。如果不按照声明次序写,会引起难以理解的错误(多个成员变量的初始化带有次序性)
例如:
初始化数组时需要指定大小,因此代表大小的那个变量必须先初始化。
Ø 由于what中的第五条引发了一个问题:如果某编译单元内的某个non-local static 对象的初始化动作使用了另一个编译单元内的某个non-local static对象,它所用到这个对象可能尚未被初始化,这就带来不明确的行为。所以必须解决相互依赖的类对象间的初始化。
How
1. 为避免需要记住变量何时需要初始化,何时不需要初始化,最简单的做法就是:总是初始化。
2. 为内置型对象进行手工初始化。
3. 构造函数最好使用成员初始化列表(member initialization list),而不要再构造函数本体内使用赋值(assignment).
4. 初始化列表中成员初始化顺序遵循声明的顺序,避免晦涩的错误。
5. 由于what中的第7点,我们将每一个non-local static对象放到自己的专属函数内(该对象在此函数内被声明为static),这些函数返回一个reference指向它所含的对象。将“函数调用”返回一个reference指向local static对象替换“直接访问non-local static对象”。你就保证了用到该对象时,它已被初始化,而且从未调用是,还不会引发构造函数成本(non-local static却不是这样高效)。这在模式设计中是Singleton(单例)模式的一种常见手法。
6. 上点中local static reference-returning函数往往结构就是这么简单(看下面图片例子):第一行定义并初始化一个local static对象,第二行就返回它。在被频繁调用的时候,inline的修饰是最佳的选择。
7. 在多线程模式下,任何一种non-const static对象,不论是local还是non-local,等待某事的发生都会有麻烦。处理这个麻烦的一种做法:在程序的单线程启动阶段(single-threaded startup portion)手工调用所有上一点的函数,这可消除与初始化有关的“竞速形式(race conditions)”。
#include <iostream>
using namespace std;
class LocalStaticClass
{
public:
LocalStaticClass(){cout<<"Local static object constructed"<<endl;}
~LocalStaticClass(){cout<<"Local static object destructed"<<endl;}
};
class ClassInnerClass
{
public:
ClassInnerClass(){cout<<"Class inner object constructed"<<endl;}
~ClassInnerClass(){cout<<"Class inner object destructed"<<endl;}
};
class FileStaticClass
{
public:
FileStaticClass(){cout<<"File Static object constructed"<<endl;}
~FileStaticClass(){cout<<"File Static object destructed"<<endl;}
};
class WrapperClassA
{
public:
WrapperClassA(){}
LocalStaticClass & singleton()
{
static LocalStaticClass LocalStaticObj; //local static object
return LocalStaticObj;
}
};
// class with non-local static object
class WrapperClassB
{
public:
WrapperClassB(){ }
~WrapperClassB(){ }
//static data member declaration
static ClassInnerClass ClassInnerObjB;//static声明
};
ClassInnerClass WrapperClassB :: ClassInnerObjB;//ststic定义才会初始化
FileStaticClass file_static_class;
int main( int argc,char * argv[])
{
cout<<"main() started."<<endl;
WrapperClassA objA;
//objA.singleton(); //只有去掉注释执行该语句时,innerObjA才被构造出来
cout<<"main() terminated."<<endl;
return 0;
}