C++中对象的初始化
对象的未初始化可能导致不明确的行为,因此一个良好的习惯是在使用任何对象之前先对其进行初始化。
1)区别赋值和初始化
在C++中,内置类型的初始化需要通过手工完成,否则需要调用对象的构造函数对每一个成员变量进行初始化,C++中规定:对象的成员变量的初始化动作发生在对象构造函数本体之前,因此下面构造函数中的代码就不是初始化而是赋值:
1 class PhoneNumber { ... } 2 class ABEntry 3 { 4 public: 5 ABEntry(const string& name, const string& addr, const list<PhoneNumber> phones); 6 private: 7 string abName; 8 string abAdrr; 9 list<PhoneNumber> abPhones; 10 int num; 11 }; 12 ABEntry::ABEntry(const string& name, const string& addr, const list<PhoneNumber>& phones) 13 { 14 abName = name; 15 abAddr = addr; 16 abPhones = phones; 17 num = 0; 18 }
在上面的构造函数中,除内置类型成员num之外,其他的成员变量都在其自身的默认构造函数被调用时(发生在进入ABEntry构造函数本体之前)进行了初始化,上面的构造函数
实际上是对已经初始化了的成员进行赋值而已。
2)成员初值列表
为了将上面的赋值行为和真正的初始化行为合二为一,即用上面的值对成员进行初始化,引入成员初值表(member initialization list),如下代码:
1 ABEntry::ABEntry(const string& name, const string& addr, const list<PhoneNumber>& phones) 2 :abName(name), abAddr(addr), abPhones(phones), num(0) 3 { 4 }
与第一个方式中基于赋值的构造函数相比,这里在效率上有所提高,甚至高效很多,因为第一种方式先为每一个非内置类型的成员调用默认构造函数,然后再为成员变量赋以新值,
而采用成员初值列表的方法则直接将参数传到各个成员的默认构造函数中作为copy的参数,从而省调了重新赋值的行为。成员初值列表中成员的书写顺序并没有严格规定,但其真正
初始化的顺序是与声明的顺序一致的。需要注意的是,内置类型的初始化和赋值成本几乎一致,所以选择使用成员初值列表初始化或者在构造函数本体中通过赋值初始化都是一样的
效率,但通常为了一致性而将其与其他成员变量一起在成员初值列表中初始化。
在无参数构造函数中,可以通过下列方式初始化对象成员:
1 ABEntry::ABEntry() 2 :abName(), 3 abAddr(), 4 abPhones(), 5 num(0) 6 { }
另外,如果是const或者reference的成员变量,那么就必须使用成员初值列表进行初始化,而不能赋值,如下的代码因为在构造函数本体中对const和reference成员变量进行赋值而导致出现成员未初始化的编译错误:
1 class Head4 2 { 3 public: 4 Head4(int mm, int nn, int& ll); 5 private: 6 int m; 7 const int n; 8 int& l; 9 }; 10 11 Head4::Head4(int mm, int nn, int& ll) 12 { 13 m = mm; 14 n = nn; 15 l = ll; 16 }
如果将构造函数修改如下,编译将通过:
1 Head4::Head4(int mm, int nn, int& ll) 2 :n(nn),l(ll) 3 { 4 m = mm; 5 }
当然为了一致性,最好将m也写在成员初值列表中。
综上,通常有下面四种情况必须使用成员初始化列表:
- 当初始化一个reference member时;
- 当初始化一个const member时;
- 当调用一个base class的constructor,而它拥有一组参数时;
- 当调用一个member class的constructor,而它拥有一组参数时。
以上整理子More Effective C++中文版第三版 case 4,关于不同源文件的对象的初始化次序的解决方案以后再补充。