0%

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

确定对象被使用前已被初始化

  • 对内置型数据(char, int, float, char * 等)进行手工初始化,因为C++不保证初始化它们
  • C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前
  • 对象成员变量的初始化使用成员初始化列表的方式(如果成员变量是const或者reference就一定要使用初始化列表的方式)
  • 如果class内存在许多成员变量或者是从多个基类继承来的,而且这个class有多个构造方法,导致成员初始化列表存在许多重复,这种情况下可以合理地在初始列中挑出那些“赋值操作的性能和初始化一样好”的成员变量,改用它们的赋值操作,并将那些赋值操作移往某个函数(通常为private),供所有的构造函数调用
  • C++有十分固定的成员初始化次序,base class总是早于derived class被初始化,class的成员变量总是以其声明次序被初始化,即使它们在成员初始化列表中以不同的次序出现,所以当你在成员初始值列中列出各个成员时,最好总是与其声明次序一致

不同编译单元内定义的non-local static对象的初始化次序

函数内的static对象称为local static对象,其他static对象成为non-local static对象。

所谓编译单元是指产出单一目标文件(single object file)的那些源码,基本上它是单一源码文件加上其所含入的头文件。

现在问题涉及至少两个源码文件,每一个内含至少一个non-local static对象,如果某个编译单元内的某个non-local static对象的初始化动作使用了另一编译单元内的某个non-local static对象,它所用到的这个对象可能尚未被初始化,因为C++对“定义于不同编译单元内的non-local static对象”的初始化次序并无明确定义。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//in a.h
class FileSystem {
public:
std::size_t numDisks() const;
};

extern FileSystem tfs;//a non-local static object

//in b.h
class Directory {
public:
Directory( params );
};
//in b.cpp
#include "b.h"
#include "a.h"
Directory::Directory( params ) {
std::size_t disks = tfs.numDisks();//use tfs
}

//in main.cpp
Directory tmpDirectory( params );//tempDirectory is also a non-local static object

int main() {
...;
return 0;
}

在初始化tmpDirectory时需要用到tfs,这时候tfs可能还没有被初始化,这就会出现问题。

解决办法:将每个non-local static对象放到一个属于它自己的专属函数内,成为一个local static对象,这些函数返回一个reference指向它所含的对象。

接上例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//in a.h
class FileSystem{ ... };
FileSystem& tfs() {
static FileSystem fs;
return fs;
}

//in b.h
class Directory{ ... };

//in b.cpp
Directory::Directory( params ) {
std::size_t disks = tfs().numDisks();
}

//main.cpp
Directory& tmpDir() {
static Directory td( params );
return td;
}

int main() {
...;
temDir().doSomething();
return 0;
}

这种方法的基础在于:C++保证,函数内的local static对象会在“该函数被调用期间”“首次遇上该对象的定义式”时被初始化。这种方式也是Singleton模式的一个常见实现方法。