Effective C++ 条款05:了解C++默默编写并调用的函数
结论:
通常,概念上,如果类的设计者自己没有声明,编译器会自动生成编译器版本的默认构造函数、拷贝构造函数、拷贝赋值函数、析构函数;而实际上,编译器只在需要调用相应函数的时候才会生成响应的函数;
(一)、默认构造函数
(1)合成时机:
- 类中包含成员对象,且该成员对象的类有默认构造函数(手动声明或者编译器合成);
- 派生自带有默认构造函数的基类;
- 带有虚函数或者虚基类的类的类;
(2)构造函数的扩充:
如果类的设计者已经实现了一个或多个重载版本的构造函数,那么,如果该类满足(1)中提到的条件,编译器会为每一个版本的构造函数(包括拷贝构造函数)安插额外的代码,用以完成:
- 对象成员默认构造函数的调用;
- 基类默认构造函数的调用;
- 虚函数表和虚基类相关的指针的初始化;
(二)、拷贝构造函数
(1)概念上,如果类的设计者没有声明一个拷贝构造函数,编译器总是会生成一个拷贝构造函数;然而,实际上,只有在一个类不具有逐比特复制的特性时,才会真正合成拷贝构造函数,在以下情况下,逐比特复制的特性会被抑制:
- 内含成员对象,且声明了拷贝构造函数(编译器合成或者显式定义)
- 派生自声明了(合成或者自定义)拷贝构造函数的基类
- 含有虚函数或者虚基类
(2)如果类的设计者手动声明了一个拷贝构造函数,则编译器的合成行为就会被抑制;这时,需要注意的是,一旦手动实现拷贝构造函数,必须要保证类中所有的成员都被正确的复制,包括对象成员、基类子成分(通过初始化列表中显示调用成员对象、基类的拷贝构造函数);否则,编译器的行为将是(一)-(2)中叙述的,为拷贝构造函数安插其成员对象和基类的默认构造函数;
(三)、拷贝赋值函数
(1)合成时机与拷贝构造函数一致,在非虚拟继承体系下,合成的函数形式与拷贝构造函数的行为基本一致;
(2)如果类的设计者手动声明了operator=(),同样需要保证所有成员都能被正确的复制;
(四)、析构函数
(1)通常只有两种情况必须声明析构函数:
- 类中含有额外申请的资源需要释放,如额外new的空间,申请的锁等;
- 作为多态使用的基类需要声明虚拟析构函数;
(2)在没有声明析构函数的情况下,如果一个类有以下两种情况,编译器会合成一个析构函数:
- 类的成员对象含有一个析构函数;
- 类的基类含有析构函数