Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

readme.md

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

关于"将对象初始化"这件事, 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++中的成员初始化次序十分固定: 基类中的成员总是先于派生类成员被初始化, 而类内的成员总是以其声明的次序被先后初始化. 为避免你或你的检阅者迷惑, 并避免某些可能存在的隐晦错误, 当你在成员初值列中罗列各个成员时, 最好总是以其声明次序为次序.

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

所谓编译单元是指产出单一目标文件(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对象.