《Effective C++》第三版——构造、析构、赋值运算
- 《Effective C++》第三版
注意:《Effective C++》不涉及任何 C++11 的内容,因此其中的部分准则可能在 C++11 出现后有更好的实现方式。
条款 5: 了解 C++ 默默编写、调用哪些函数
编译器可以暗自为 class 创建 default 构造函数、copy 构造函数、copy assignment 操作符、析构函数
如果某个类没有声明 copy 构造函数、copy assignment 操作符、析构函数,编译器会声明它们;如果某个类没有声明任何构造函数,编译器会声明默认构造函数。编译器声明的这些函数,只有在被调用的时候才会被创建。
条款 6:若不想使用编译器自动生成的函数,就该明确拒绝
为驳回编译器自动提供的技能,可将相应的成员函数声明为 private 并且不予实现。使用像 uncopyable 这样的 base class 也是一种方法
C++11 引入了 =delete
,似乎更适合完成这个工作。
条款 7:为多态基类声明 virtual 析构函数
带有多态性质的 base class 应该声明一个 virtual 析构函数
因为具有多态性质的继承体系中,我们常常使用 base 指针指向 derived 对象,如果此时 base class 的析构函数为 non-virtual 的,则结果未定义。
Class 如果不是作为 base class,或者不具备多态性质,就不该声明 virtual 析构函数
拥有 virtual 函数会使得对象必须额外携带某些信息,这些额外开销可能是不必要的。
一般而言,当 class 至少含有一个 virtual 函数时,才将析构函数声明为 virtual。
条款 8:别让异常逃离析构函数
析构函数绝对不要吐出异常。如果一个析构函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们或结束程序
考虑下面的情况:
void f(){
vector<A> va; // A是一个析构函数可能抛出异常的类
} // va在这里被销毁
如果 va 中有多个元素在析构过程中均抛出异常,将导致程序终止或者未定义行为。
如果某个类的析构函数可能抛出异常,有两种解决方法:
- 直接调用
std::abort()
结束程序 - 吞下异常
需要指出的是,这两种做法都不值得推荐,因为它们无法真正地处理异常。
如果客户需要对某个操作函数运行期间的异常做出反应,那么 class 应该提供一个普通函数执行该操作
如果一个析构函数的某个操作,例如调用 close()
,可能出现异常,就将这个调用操作移到一个普通函数里,让客户自己去调用。
条款 9:绝对不在构造或析构函数中调用 virtual
在构造和析构期间不要调用 virtual 函数,因为这类调用从不下降至 derived class
在一个多态继承体系中,如果 base 的构造函数中调用了 virtual 函数,在 derived 对象构造 base 对象时,base 构造函数调用的实际上是 base 版本的 virtual 函数,这是因为:derived class 对象在构造 base class 的期间,对象的类型是 bass class 而不是 derived class。
析构函数也同理。
条款 10:令 operator=
返回一个 reference to *this
因为这样可以支持连续赋值。
条款 11:在 operator=
中处理自我赋值
确保当对象自我赋值时 operator=
有良好行为。其中技术包括比较 source 和 target 的地址、静心周到的语句顺序以及 copy-and-swap
这里介绍一下 copy-and-swap 技术:
A& A::operator=(A rhs){ // 注意,这里巧妙地利用值传递实现copy
swap(rhs); // *this.swap
return *this
}