初始化列表的使用

下列情况中, 为了让程序顺利编译, 必须使用 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;
}

 

posted @ 2014-11-16 15:52  wu_overflow  阅读(285)  评论(0编辑  收藏  举报