深入C++对象模型(5) -- 成员初始化列表

一、什么是初始化列表?

C++类的成员变量初始化有两种途径,第一种是在类的constructor函数本体内,另一种途径就是经由初始化列表(initialization list)。如:

在构造函数体内初始化:

class Person
{
public:
    Person()
    {
        m_name = 0;
        m_age = 0;
    }
private:
    String m_name;
    int m_age;
};

使用初始化列表初始化:

class Person
{
public:
    Person() : m_age(0),  m_name(0)
    {}
private:
    String m_name;
    int m_age;
};

在大多数情况下,这两种方式都是可以的,但有四种情况必须使用初始化列表。

  1. 当初始化一个reference member时;
  2. 当初始化一个const member时;
  3. 当调用一个base class的constructor,而它有一组参数时;
  4. 当调用一个member class的constructor,而它有一组参数时。

二、两种初始化方式编译器实现的差异

// **在构造函数体内初始化**
class Person
{
public:
    Person()
    {
        m_name = 0;
        m_age = 0;
    }
private:
    String m_name;
    int m_age;
};

这段程序可以编译并且正常执行,但是效率有所损耗。在这里,Person constructor会产生一个领实习的*String* object,然后将它初始化,之后以一个assignment运算符将临时的object指定给m_name,随后再摧毁那个临时性的object。以下是constructor内部扩张的结果:

// C++ 伪代码
Person()
{
    //调用String的缺省构造函数
   m _name.String::String();

    //产生临时性对象
    String temp = String(0);

    //逐一成员地拷贝 _name
    m_name.String::operator=( temp );

    //摧毁临时性对象
    temp.String::~String();

    m_age = 0;
}

而使用初始化列表的方式,如

//**使用初始化列表初始化**
class Person
{
public:
    Person() :  m_age(0) , m_name(0)
    {}
private:
    String m_name;
    int m_age;
};

其构造函数扩张的结果是:

// C++ 伪代码
Person()
{
    //调用String(int) 构造函数
    m_name.String::String(0);
    m_age = 0;
}

三、编译器对初始化列表做了哪些事

编译器会一一操作initialization list,以适当的顺序在constructor之内安插初始化操作,并且在任何用户写的代码之前。要注意的是:list中的项目顺序是由class中的member声明顺序决定的,不是由initialization list中的排列顺序决定的。在本例的Person class中,m_name被声明与m_age之前,所以它的初始化比m_age早。

四、初始化列表的陷阱

“初始化顺序”与“initialization list中的项目排列顺序”之间的外观错乱,会导致下面意向不到的危险:

class X
{
public:
    X(int val): j(val), i(j)
    {}
public:
    int i;
    int j;
};

上述程序代码看起来像是要把j设置初始值为val,再把i的初始值设置为j。问题在于,由于声明的顺序缘故,initialization list中的i(j) 其实比j(val)更早执行。但是因为j一开始未初始化,所以i(j)的执行结果将导致i无法预知其初值。并且编译器一般不会报错和给出警告。(只有g++这个编译会给出警告!)如何避免这个陷阱呢?
建议:把一个member的初始化操作和另一个放在一起,放在constructor之内,如下。

    X(int val): j(val)
    { i = j;} // i并不是由构造参数直接初始化,将其放在构造函数体内,而不是初始化列表中

下面给出有陷阱的代码和修改好的代码:

lass X
{
public:
    X(int val): j(val), i(j)
    {}
public:
    int i;
    int j;
};

class Y
{
public:
    Y(int val): j(val)
    {
        i = j;
    }
public:
    int i;
    int j;
};

int main()
{
    X x88888888888888(3);
    Y y(5);

    std::cout << "x.i = " << x.i << " x.j = " << x.j << std::endl;
    std::cout << "y.i = " << y.i << " y.j = " << y.j << std::endl;

    return 0;
}


运行结果(vc++):

 以上运行结果说明两个问题:

  1. 初始化列表的执行顺序与列表中项目的顺序无关,而是由成员变量的声明顺序决定的;
  2. 初始化列表执行的初始化代码是安插在用户写的代码之前的。(因为,在类Y中,若j(val)被安插在i=j之后,那么i必将是无法预知的值,因为执行这一句时,j还未被初始化,然而,执行的结果是正确。说明j(val)被安插在i=j之前)


五、总结


简略地说,编译器会对initialization list 一一处理并可能重新排序,以反映出menbers的声明顺序,编译器会安插一些代码到constructor体内,并置于任何用户写的代码之前。

  1. 初始化列表和构造函数内初始化大多数情况下都是可以用的,除了四种情况必须使用初始化列表;
  2. 初始化列表比直接在构造函数内初始化效率要高;
  3. 初始化列表执行的顺序是由成员变量声明的顺序决定的,而与初始化列表的元素顺序无关;
  4. 在执行顺序上,初始化列表的执行比构造函数体内初始化操作要早**
     
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值