条款04:确定对象被使用前已被初始化

来源:互联网 发布:Date java 编辑:程序博客网 时间:2024/06/05 19:15

条款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对象是指存储区不属于stackheap"寿命"从被构造出来直至程序结束为止的对象。这些对象包括全局对象,定义于namespace作用域的对象,在classfunction以及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 objectclass 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;}




0 0