第2
章:构造、析构、赋值运算
条款05
:了解C++
默默编写并调用哪些函数
编译器会暗自为
class
创建default
构造函数、copy
构造函数、copy assignment
操作符以及析构函数对于
class
内含reference
成员或const
成员,编译器拒绝为其生成copy
构造函数和copy assignment
操作符因为
C++
不允许reference
改指向不同的对象以及更改const
成员。
条款06
:若不想使用编译器自动生成的函数,就该明确拒绝
为驳回编译器自动提供的函数,可将相应的成员函数声明为
private
并且不予实现。掌握
Uncopyable
类的实现机制将构造函数和析构函数设置为
protected
的将拷贝构造函数和拷贝赋值运算符设置为
private
的1
2
3
4
5
6
7
8
9
10class Uncopyable
{
protected:
Uncopyable() {}
~Uncopyable() {}
private:
Uncopyable(const Uncopyable &);
const Uncopyable &operator=(const Uncopyable &);
};
条款07
:为多态基类声明virtual
析构函数
带多态性质的
base class
应该声明一个virtual
析构函数当
class
内至少含有一个virtual
函数,才为它声明virtual
析构函数class
的设计目的如果不是作为base class
使用,或不是为了具备多态性,就不该声明virtual
析构函数然而,有时候你希望拥有一个抽象类,但没有任何需要的
pure virtual
方法,怎么办?由于
abstract class
(不能实例化)总是被期望当作多态基类,多态基类又需要virtual
析构函数,而pure virtual
函数会导致abstract class
,因此可将析构函数声明为pure virtual
并且给出默认实现。1
2
3
4
5
6
7
8// 小技巧:pure virtual 析构函数
class AWOV
{
public:
virtual ~AWOV() = 0;
};
AWOV::~AWOV() {/* default */}
条款08
:别让异常逃离析构函数
- 析构函数绝对不要吐出异常
假设有一个类负责数据库的连接:
1 | class DBConnection |
为了确保客户不忘记在DBConnection
对象上调用close
函数,一个合理的想法是创建一个用来管理DBConnection
资源的类,并在析构函数中调用close
。
1 | class DBConn |
用户可以写出这样的代码
1 | { |
如果被析构函数调用的函数close
可能抛出异常,析构函数应该捕获异常然后吞下它们或者结束程序。
一个好的策略是,开放一个close
接口供用户调用,把调用close
的责任从DBConn
析构函数手上移到用户手上。
1 | class DBConn |
因此,如果客户需要对某个操作函数运行期间的异常作出反应,那么class
应该提供一个接口执行该操作。如果close
的确发生了异常,而客户没有调用close
接口进行处理,DBConn
只能吞下或结束程序。
条款09
:绝不在析构和构造函数中调用virtual
函数
- 派生类对象内的基类成分会在派生类自身成分被构造之前先被构造
- 基类构造期间,虚函数绝不会下降到派生类层
需要注意的是,有时类有多个构造函数,每个都需要执行某些相同的工作,那么避免代码重复时会把相同的初始化代码放到一个init
函数中实现,如果这时在init
函数中同样调用了虚函数,情况是一样的但比较隐秘。
条款10
:令赋值操作符operator=
返回一个reference to *this
- 为了实现连续赋值
条款11
:在operator=
中处理自我赋值
- 有些自我赋值并不明显,如通过指针或引用
假设你建立一个class
来保存一个指针指向一块动态分配的位图(bitmap
):
1 | class Bitmap {...}; |
错误的operator=
实现为:
1 | // 自我赋值不安全 |
可通过一个“证同测试”来检验:
1 | // 自我赋值安全, 但不具备异常安全 |
所谓的异常安全指的是,如果new Bitmap
发生异常,会导致Widget
最终会持有一个指针指向一块被删除的Bitmap
。
1 | // 具备异常安全, 则自动具备自我赋值安全 |
使用更好的copy and swap
技术:
1 | class Bitmap {...}; |
条款12
:复制对象时勿忘每一个成分
- 每一个成分包括对象内所有成员变量以及所继承的基类成分
- 在拷贝构造函数中的初始化列表中调用所继承的类的拷贝构造函数
- 在拷贝赋值操作符函数中调用所继承的类的拷贝赋值操作符函数
- 不要尝试让拷贝构造函数和拷贝赋值操作符函数互相调用