关于"将对象初始化"这件事, C++似乎反复无常. 除了花时间去记住各种情况下变量是否会被自动初始化, 另外一个稳妥的处理办法是: 永远在对象被使用之前手动将它初始化.
对于内置类型, 你需要在使用前通过定义式或赋值式或外部输入对其进行赋值;
对于内置类型之外的任何东西, 则需要通过编写合适的构造函数, 以确保每一个构造函数都将对象的每一个成员初始化.
通过构造函数为对象内的各个成员赋初值的方式有两种:
- 在构造函数体内使用赋值语句;
class Student {
public:
Student(const string& name, int age, const map<string, int>& scores) {
name_ = name;
age_ = age;
scores_ = scores;
}
...
private:
string name_;
int age_;
map<string, int> scores_; // 每个科目(string类型)对应一个成绩(int类型)
};- 使用成员初始化列表;
Class Student {
public:
Student(const string& name, int age, const map<string, int>& scores):
name_(name), age_(age), scores_(scores) {}
...
private:
string name_;
int age_;
map<string, int> scores_; // 每个科目(string类型)对应一个成绩(int类型)
};使用成员初始化列表为对象的数据成员赋初值通常具有更高的效率. 对于非内置类型数据成员, 使用成员初始化列表只需要进行一次copy构造, 而使用赋值语句则需要首先经由一次default构造再通过赋值运算符用新值覆盖其默认值. 对于内置类型数据成员来说, 两种初始化方式的成本相同, 但为了一致性, 我们在使用成员初始化列表时, 通常将所有类型的数据成员均通过列表初始化.
请立下一个规则: 规定总是在初值列中列出所有数据成员, 以免还得记住哪些数据成员(如果它们在初值列中被遗漏的话)可以无需初值.
当数据成员是const或者references时, 必须使用成员初值列.
为避免需要记住成员变量何时必须在成员初值列中初始化, 何时不需要, 最简单的做法是: 总是使用成员初值列. 这样做有时候绝对必要, 且又往往比赋值更高效.
C++中的成员初始化次序十分固定: 基类中的成员总是先于派生类成员被初始化, 而类内的成员总是以其声明的次序被先后初始化. 为避免你或你的检阅者迷惑, 并避免某些可能存在的隐晦错误, 当你在成员初值列中罗列各个成员时, 最好总是以其声明次序为次序.
所谓编译单元是指产出单一目标文件(single object file)的那些源码. 基本上它就是一个单一源文件加上它所包含的头文件.
所谓static对象, 其声明周期从被构造出来一直到程序结束时为止. 它包括全局(global)对象, namespace作用域内的对象, classes内/函数内/文件作用域内的被声明为static的对象. 函数内的static对象被称为local static对象(因为它对函数而言是local), 其它的均称为non-static对象.
现在描述这样一个情形: 现有至少两个源码文件, 每一个内至少含有一个non-local static对象. 当某个编译单元内的某个non-local static对象的初始化动作使用了另一个编译单元内的某个non-local static对象时, 它所用到的这个对象可能尚未被初始化, 因为C++对"定义于不同编译单元的non-local static对象"的初始化次序并无明确定义.
解决办法是: 将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static). 这些函数返回一个reference指向它所含的对象. 然后用户调用这些函数, 而不是直接指涉这些对象. 通过这种方式, non-local static对象被转换成了local static对象. 而由于C++保证函数内的local static对象会在"该函数被调用期间""首次遇到该对象的定义式"时才被初始化, 也就间接地保证了其初始化次序.
这种解决方式在"单例模式(singleton)"这一设计模式中经常看到. 本目录下的singleton.cc文件包含一个简单的单例模式示例.
- 为内置型对象进行手工初始化, 因为C++不保证初始化它们.
- 构造函数最好使用成员初值列(member initialization list), 而不要在构造函数体内使用赋值操作. 初值列列出的成员变量, 其排列次序应该与它们在类中的声明次序相同.
- 为免除"跨编译单元的初始化次序"问题, 请以local static对象替换non-local static对象.