初始化列表的使用
下列情况中, 为了让程序顺利编译, 必须使用 member initialization list:
1. 初始化一个 reference member 时;
2. 初始化一个 const member 时;
3. 当调用一个 base class 的 constructor, 而它拥有一组参数时;
4. 当调用一个 memeber class 的 constructor, 而它拥有一组参数时.
考察以下代码:
class Word { String _name; int _cnt; public: //没有错误, 只是效率低 Word() { _name = 0; _cnt = 0; } };
这种情况下, Word constructor 会先产生一个暂时性的 String object, 然后将它初始化, 再以一个 assignment 运算符将暂时性的 object 指定给 _name, 然后再摧毁这个暂时性的 object, 以下是constructor 可能的内部扩张结果:
//可能的结果 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; } //它会被扩张成这个样子 //可能的代码 Word::Word(/* this pointer goes here */) { //调用 String(int) constructor _name.String::String(0); _cnt = 0; }
这又引起另外一个问题, 是否每个 member 都必须使用 member initialization list 来初始化呢?
initialization list 不是一组函数, 编译器对于 initialization list , 会以适当次序在 constructor 之内安插初始化操作, 并且在任何 explicit user code 之前(例如之前 class Word 的初始化) 重点在于, 初始化的顺序是由 class 中的 members 声明的顺序决定, 不是由 initialization list 中的排列次序决定, 这就可能导致以下的问题:
class X { int i; int j; public: //buggy, i(j) 会产生不确定行为 X(int val):j(val), i(j) {} }; //较好的处理方式 X::X(int val):j(val) { i = j; }
还有一个有趣的问题, initialization list 中的项目被安插到 constructor 的函数体中, 会继续保存声明次序吗? 也就是说, 对于前一个代码, j 的初始化操作会安插在 explicit user assignment 操作 ( i = j ) 之前还是之后? 如果继续保存, 则这样操作也会导致不确定行为. 然而以上的代码是正确的, 因为 initialization list 的项目被放在 explicit user code 之前.
另一个常见的问题是, 能否像下面那样, 调用一个 member function 以设定一个 member 的初值:
//X::XFoo() 被调用 X::X(int val): i(XFoo(val), j(val)) {}
答案是肯定的, 但是, 值得注意: 请使用 存在于 constructor 函数体内的一个 member, 而不要使用 存在于 initialization list 中的 member 设定初值. 因为你并不知道 XFoo() 对于 X object 的依赖程度有多高, 如果把 XFoo() 放在 constructor 体内, 那么对于到底是哪一个 member 在 XFoo() 执行时被设立初值这件事, 就不会导致模棱两可的情况.
用 member function 来初始化是合法的, 它会被编译器扩充为:
// constructor 被扩充的结果 X::X(/* this pointer */ int val) { i = this->XFoo(val); j = i; }
那么如果一个 derived class member function 被调用, 其返回值被当作 base class constructor 的一个参数, 将会如何:
class FooBar: public X { int _fval; public: int fval(){return _fval;} FooBar(int val): _fval(val), X(fval()) //使 fval() 作为 base class constructor 的参数 {} ... }; //这是个好主意吗? //这是可能的扩张结果 FooBar::Foobar(/* this pointer goes here*/) { //编译器会把 initialization list 的代码扩展到 user code 的 前面 X::X(this, this->fval()); //wtf, 导致不明确行为 _fval = val; }