【C++】深度探索C++对象模型读书笔记--构造函数语义学(The Semantics of constructors)(四)

成员们的初始化队伍(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之前。

原文地址:https://www.cnblogs.com/vincently/p/4646945.html