深度探索C++对象模型之第二章:构造函数语意学之成员初始值列表
当我们需要设置class member的初值时,要么是经过member initialization list ,要么在construcotr内。
一、先讨论必须使用member initialization list的四种情况:
在以下四种情况,为了能够让程序被顺利编译,必须使用member initialization list:
- 当初始化一个reference mmember时
- 当初始化一个const member时
- 当调用一个base class的constructor时,并且基类有一组参数
- 当调用一个member class的constructor时,并且成员类也有一组参数
虽然在四种情况下,使用构造函数初始化成员,程序可以被正确编译并执行,但是效率不高,举例如下:
1 class Word{ 2 String _name; 3 int _cnt; 4 public: 5 //此时是在构造函数内执行数据成员的初始化 6 Word() { _name = 0; _cnt = 0;} 7 };
上例对应四种情况的第四种,在这里Word constructor会先产生一个临时String object,然后将它初始化,之后以一个赋值运算符将临时性object指定给_name,然后再将那个临时String object摧毁(析构)。以下是编译器对Word constructor扩张的结果:
1 //C++伪代码 2 Word::Word() 3 { 4 //调用Stringd的default constructor 5 _name.String::String(); 6 7 //产生临时对象 8 String temp = String(0); 9 10 //memberwise的拷贝_name 11 _name.String::operator = (temp); 12 13 //摧毁临时对象 temp 14 temp.String::~String(); 15 16 _cnt = 0; 17 18 }
如果在成员初始值列表中进行成员初始化操作,明显更有效率,即如下所示:
1 Word::Word: _name(0){ 2 _cnt = 0; 3 4 } 5 6 //将会被扩展成下面形式: 7 Word::Word() 8 { 9 //调用String(int)构造函数 10 _name.String::String(0); 11 _cnt = 0; 12 }
二、讨论下成员初始值列表中初始化的顺序:
举个例子:
Word::Word() : _cnt(0),_name(0) { }
对于上述构造函数,编译器会一一执行初始值列表,以适当顺序(这个顺序是由class中member的声明顺序决定的,而不是initialization list的顺序决定的)在构造函数内安插初始化操作。编译器在constructor的扩展如下:
1 Word::Word () 2 { 3 _name.String::String(0); 4 _cnt = 0; 5 }
由初始化顺序可能会导致一个意想不到的错误:如下所示:
1 class X{ 2 int i; 3 int j; 4 public: 5 X(int val) 6 :j(val),i(j) {} 7 8 };
在上述代码中,初始化的顺序应该是先初始i,因为i声明在前,但是因为j一开始没有初值,所以i(j)的结果会导致i无法预知其值。
所以我们可以将i的初始化操作放在构造函数内部,此时initialization list的执行顺序一定在explicit user code之前。如下所示:
X::X(int val) : j(val) { i = j;}
三、再来讨论其他两个关于初始化位置的问题:
其一是、当调用一个成员函数来初始化一个成员的值时,但是请使用构造函数内的member,而不是member initialization list中的member,如下所示:
X::X(int val) : i(xfoo(val)),j(val) {}
此时和此object相关的this指针,已经被构建妥当,编译器将constructor扩张为如下形式:
1 X::X () 2 { 3 i = this->xfoo(val); 4 j = val; 5 }
另一情况是还是不要使用派生类的成员函数作为基类构造函数的参数了。不太好