构造函数模型
默认构造函数
当你没有定义任何构造函数的时候,编译器会为你生成一个默认构造函数。但是,这个构造函数并非是真正被编译器变成一段实体的代码,在普通情况下的,比如:
class A1{
public:
int a;
char* ch;
};
上面这个类没有任何的构造函数,编译器会在需要的时候提供四个函数——默认构造函数,复制构造函数,析构函数和赋值运算符。
但实际上,编译器生成的这个implicit的默认构造函数是trivial的,所以他实际上没有被编译器合成出来。
当你在栈上创建一个A1对象时,他只是划分好空间,没有进行初始化,所以A1内存的实际表示是创建A1之前的内存。
初始化是程序员的工作,编译器不会帮你实现。
有4种情况编译器会帮助你生成一个nontrivial的默认构造函数
- 类含有带默认构造函数的类成员对象
- 基类含有默认构造函数
- 有一个虚基类的类
- 和含有虚函数的类
在这上面的四种情况中,你必须要么显式调用类成员或基类的默认构造函数来构造派生类,要么就需要初始化虚表的指针或者实现有关虚基类所必须的数据。
复制构造函数
编译器也依靠复制构造函数是否是trivial或nontrivial来决定该构造函数是否需要实现,而这个条件就看类的复制是否是bitwise的。
以下四种情况是非bitwise的
- 类含有一个有显式复制构造函数的类成员对象
- 基类含有显式复制构造函数
- 含有虚函数的类
- 类基类中含有自虚基类。
前两种情况中,程序必须递归调用成员或基类的复制构造函数来完成复制操作,所以编译器或实现一个nontrivial的复制构造函数。
对于情况3,含有虚函数的类。
如果我们用派生类对象当作参数生成一个基类对象,很明显,基类的虚表指针不能指向派生类的虚表。这个时候编译器会实现一个复制构造函数来让基类对象拥有自己的虚表和虚表指针。
初始化列表
下列情况中,你必须使用初始化列表:
- 初始化一个引用成员
- 初始化一个常量成员
- 调用基类的带参构造函数
- 调用类成员的带参构造函数
额外的,你还要注意。初始化列表的初始化顺序是按照类内成员声明的顺序实现的,而非列表中的顺序,所以你需要额外注意初始化的先后问题。比如:
class A2{
private:
int a;
int b;
public:
A2():b(10),a(b){}
}
初始化A2对象的时候首先会对a初始化,因为a最先被声明,但是b此时还尚未初始化,所以会导致未知行为。