条款08:别让异常逃离析构函数
-
C++并不禁止析构函数抛出异常,但是不建议这样做
-
有两个异常存在的情况下,程序不是结束执行就是导致不明确的行为
如果无法避免析构函数产生异常的可能性,有两种办法:
1、若析构函数爬出异常,就调用abort结束程序
A::~A() {
try {
a.func();}
catch(...) {
//记录调用a.func()过程中出现异常
abort();
}
}
2、吞下异常,当做什么也没发生过
A::~A() {
try {
a.func();}
catch(...) {
//记录调用a.func()过程中出现异常
}
}
条款09:绝不在构造和析构过程中调用virtual函数
子类对象调用子类构造函数之前,会先调用父类构造函数。若子类重写了父类中的一个虚函数func(),父类的构造函数中调用了func()函数,那么当子类对象调用父类的构造函数执行到这一句时,会调用父类版本的func()函数。
这是因为:
- 父类的构造函数的执行早于子类的构造函数,当父类的构造函数执行时,子类的成员变量尚未初始化,如果此时调用的虚函数下降至子类层次,会存在使用子类未初始化成员的风险,因此C++禁止这种危险。
更根本的原因是:
- 子类对象在调用父类构造函数期间,对象类型是基类而不是子类,不仅虚函数会使用父类版本,此时使用dynamic_cast和typeid,也会把对象视为基类类型
析构函数也是一样的原因
条款10:令operator=返回一个绑定到*this的引用
形式:
class A {
public:
...
A& operator=(const A& other) {
...
return *this;
}
}
这是为了实现连锁赋值,即:
int x, y, z;
x = y = z = 100;
//运行原理
x = (y = (z = 100))
条款11:在operator=中处理自我赋值
自我赋值的意思就是:
等号右边的东西和等号左边的东西是同一份(地址相同)
a[i] = a[j];// i == j成立
一般的做法是在赋值前做一个检查:
A& A::operator=(const A& other) {
if (this == &other) // 比较双方地址
return *this;
delete pb;
pb = new B(*other.pb);
return *this;
}
条款12:赋值对象时勿忘其每一个成分
复制函数包括:拷贝构造函数、拷贝赋值运算符
当自定义了复制函数后,编译器不会再生成补充版本(即便自定义的有问题)
- 当为class添加一个成员变量,必须同时修改复制函数
- 为子类自定义复制函数时,要注意对基类成员的复制。基类成员通常是private,子类无法直接访问,应该让子类的复制函数调用相应的基类复制函数
不能为了简化代码,就在拷贝构造函数中调用拷贝赋值运算符,也不能在拷贝赋值运算符中调用拷贝构造函数。因为构造函数用来初始化新对象,而赋值运算符只能用于已初始化的对象之上。
要想消除复制函数的重复代码,可以建立一个新的成员函数给复制函数调用,这个函数通常是private且命名为init