成员们的初始化队伍(member Initia
有四种情况必须使用member initialization list:
1. 当初始化一个reference member时;
2. 当初始化一个const member时;
3. 当调用一个base class的constructor,而它拥有一组参数时;
4.当调用一个member class的constructor,而它拥有一组参数时;
在这四种情况下,程序可以被正确编译运行,但是效率不高。例如:
1 class Word { 2 String name; 3 int _cnt; 4 public: 5 Word() { 6 _name = 0; 7 _cnt = 0; 8 }
在这里,Word constructor会产生一个临时性的String object,然后将它初始化,之后以一个assignment运算符将临时性object指定给_name,然后再摧毁那个临时性object。
以下是constuctor可能的内部扩张结果:
Word::Word(/* this pointer goes here*/) { //调用String的default constructor _name.String::String(); //产生临时对象 String temp = String(0); //"memberwise"拷贝_name _name.String::operator=(temp); //摧毁临时对象 temp.String::~String(); _cnt = 0; }
对代码反复审查并修正,得到一个明显更有效率的实现方法:
//较佳的方式 Word::Word: _name(0) { _cnt = 0; }
它会扩张成这个样子:
1 Word::Word(/*this pointer goes here*/) 2 { 3 //调用String(int)constructor 4 _name.String::String(0); 5 _cnt = 0; 6 }
对于member initialization list的每一个成员,编译器会根据member在类中声明的顺序在constructor之内安插代码,这些代码在任何explicit user code之前。
初始化顺序和initialization list中的项目排列顺序之间的外观错乱,会导致下面意想不到的危险:
class X { int i; int j; public: X(int val): j(val), i(j){} }
上述代码看起来像把j设初值为val,再把i设初值为j。问题在于,由于声明顺序的缘故,initialization list中的i(j)其实比j(val)更早执行。但因为j一开始未有初值,所以i(j)的执行结果导致i无法预知其值。所以建议总是把一个member的初始化操作和另一个放在一起,放在constructor之内,像下面这样:
X::X(int val) : j(val) { i = j; }
这里还有一个有趣的问题。initialization list中的项目被安插在constructor中,会继续保持声明顺序吗?也就是,已知:
X::X(int val) : j(val) { i = j; }
上述的代码正确吗?答案是:yes。因为initialization list的项目被放在explicit user code之前。
简略地说,编译器会对initialization list一一处理并可能重新排序,以反映出members的声明顺序。它会安插一些代码到constructor 体内,并置于任何explicti code之前。