第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:复制对象时勿忘每一个成分
- 每一个成分包括对象内所有成员变量以及所继承的基类成分
- 在拷贝构造函数中的初始化列表中调用所继承的类的拷贝构造函数
- 在拷贝赋值操作符函数中调用所继承的类的拷贝赋值操作符函数
- 不要尝试让拷贝构造函数和拷贝赋值操作符函数互相调用