在C++中,有一种特殊的成员函数也就是构造函数,只要创建类类型的新对象时,都要执行构造函数。构造函数的工作是保证每个对象的数据成员具有合适的初始值。下面主要想说三个问题:默认构造函数、初始化列表、显式构造函数。
一、默认构造函数(Default Constructor)
在《C++ Primer》中,关于默认构造函数有这么一种说法:只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。我们可以将默认构造函数分成两类:trival和nontrival,所谓trival就是没什么用的,而nontrival才有意义,是编译器所的需要的那种,必要的话由编译器合成出来。有四种情况会产生nontrival的默认构造函数。
- 带有default constructor的member class object
- 带有default constructor的base class
- 带有virtual function的class。也就是说vptr是需要由构造函数初始化的,当然这是由编译器完成的。
- 带有virtual base class的class
当然如果用户自己定义了构造函数,那么编译器将不会再自动生成。但是也不是什么也不做,而是在上面几种情况下将必要的操作插入构造函数代码中。举个例子。
1 | class A{ |
如果没有默认构造函数会有什么影响呢?其实默认构造函数的最大好处就是自动调用。当没有提供默认构造函数的时候,自动调用的情况就都出现问题了。比如动态分配class数组。
二、初始化列表(Initialization List)
初始化列表就是形如下面的初始化方式。
1 | class A{ |
其中内置数据类型直接赋值,如果成员是类的话,则调用相应的构造函数。那么初始化列表与在函数体内直接赋值有什么区别呢?比如下面两个函数。
1 | B(int a, int b):y(a),z(b){ |
区别在于第二个函数会先产生一个临时性的A object,然后将它初始化,之后以一个赋值运算符将临时性object指定给y,随后再摧毁那个临时性的object。以下是两个函数的可能的内部扩张结果。
1 | B(int a, int b){ |
值得注意的一点是,成员的初始化顺序并不初始化列表中的顺序,而是成员在类中声明的顺序。另外有时候初始化列表并非一个可选方案,而是必选。主要有下面四种情况。
- 当初始化一个reference member时;
- 当初始化一个const member时;
- 当调用一个base class 的 constructor,而它拥有一组参数时
- 当调用一个member class 的 constructor,而它拥有一组参数时。
三、显式构造函数(explicit constructor)
按照默认规定,只有一个参数的构造函数定义了一个隐式转换,将该构造函数对应数据类型的数据转换为该类对象。比如
1 | class A{ |
但有时候这种转换会带来错误和误解,此时就可以使用explicit修饰构造函数,也就是显式构造函数。
1 | class A{ |