C part of C++ 不保证发生初始化; array
non-C part of C++ 发生初始化; vector
最佳处理办法:永远在使用对象之前先将它初始化。
内置类型:手工初始化。
非内置类型:确保每一个构造函数都将对象的每一个成员初始化。
初始化不等于赋值
C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。内置类型不一定有初始化动作。
对比赋值操作与使用初始化列表进行初始化
//先调用default构造函数,然后再调用copy 赋值操作符
A::A(const std::string& name)
{
theName = name;
}
//只调用成员变量的copy构造函数
A::A(const std::string& name):theName(name)
{}
后一种方法高效得多。
PS:
- 内置对象初始化和赋值的成本相同;
- 如果成员变量属于内置类型,且成员变量为const或references,它们就一定需要初值,不能被赋值;
成员初始化次序
基类早于派生类;
成员变量以声明次序被初始化;
不同编译单元内的non-local static对象的初始化次序
C++对定义于不同编译单元内的non-local static对象的初始化次序并无明确定义。
class FileSystem{
public:
...
std::size_t numDisks() const;
...
};
extern FileSystem tfs; //non-local static 对象
class Dir{
public:
Dir(params);
...
};
Dir::Dir(params){
...
std::size_t disks = tfs.numDisks(); //使用tfs对象
...
}
Dir temDir(params); //为临时文件而做出的目录
temDir的构造函数可能会用到尚未初始化的tfs。
无法确定初始化次序。
解决办法:
将每个non-local static 对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所包含的对象。然后用户调用这些函数,而不是直接调用这些对象。
换句话说,non-local static对象被local static对象替换了。(sigleton模式的常见实现手法)
附加好处:
- 绝佳的inline候选人,尤其在它们被频繁调用的时候;
- 不调用该“仿真函数”,就绝不会引发构造和析构成本;
改动后的程序:
class FileSystem{
public:
...
std::size_t numDisks() const;
...
};
//extern FileSystem tfs; //non-local static 对象
FileSystem& tfs(){
static FileSystem fs;
return fs;
}
class Dir{
public:
Dir(params);
...
};
Dir::Dir(params){
...
//std::size_t disks = tfs.numDisks(); //使用tfs对象
std::size_t disks = tfs().numDisks(); //改为tfs()
...
}
//Dir temDir(params); //为临时文件而做出的目录
Dir& temDir(params){
static Dir dir;
return dir;
}
总结:
- 为内置型对象进行手工初始化,因为C++不保证初始化它们;
- 构造函数最好使用初始化列表,而不要在构造函数本体内使用赋值操作。初始化列表列出的成员变量,其排列次序应该和它们在class中的声明次序相同;
- 为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象;