Effective C++学习笔记(1)

创建于20180114
更新于20180122。

7.为多态基类声明virtual析构函数

概要

virtual函数的目的是允许派生类的实现得以客制化。比如父类的一个virtual函数,在不同的子类中可以有不同的实现方法。任何class只要带有virtual函数,都几乎确定应该也有一个virtual析构函数。
如果class不含virtual函数,通常表示它不打算作为一个基类,当class不打算作为一个基类,令其析构函数为virtual的做法不推荐。理由如下:
欲实现出virtual函数,对象必须携带某些信息,主要用来在运行期决定哪一个virtual函数被调用。这份信息通常是由一个vptr(virtual table pointer)指针指出。vptr指向一个由函数指针构成的数组,称为vtbl(virtual table)每一个带有virtual函数的class都有一个相应的vtbl,当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr所指的那个vtbl——编译器在其中寻找适当的函数指针。结果就是,class内含virtual函数,那么其对象的体积会增加导致的结果就是程序的可移植性差。
所以,只有当class内含至少一个virtual函数,才将它明virtual析构函数

当希望拥有抽象类,但是没有纯虚函数时,可以为这个类声明一个纯虚析构函数。

但并不是任何有继承机制的类都要有虚析构函数。本规则只适用于带多态性质的基类身上。这种基类的设计目的是为了用来通过基类接口处理派生类对象。

总结

  • 带多态性质的基类应该声明一个virtual析构函数。如果类带有任何virtual函数,它就应该拥有一个virtual析构函数。
  • 类的设计目的如果不是作为基类使用,或不是为了具备多态性,就不该声明virtual析构函数。

8.别让异常逃离析构函数

概要

C++并不禁止析构函数吐出异常,但不鼓励这样做。因为在大于一个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为。
如果某个操作可能在失败时抛出异常,而又存在某种需要处理该异常,那么这个异常必须来自析构函数之外的某个函数,因为析构函数吐出异常就是危险,总会带来“过早结束程序”或“发生不明确的行为”的风险。

总结

  • 析构函数绝对不要吐出异常,如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕获任何异常,然后吞下它们(不传播)或结束程序。
  • 如果客户需要对某个函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而不是在析构函数中)执行该操作。

9.绝不在构造和析构过程中调用virtual函数

概要

在一个继承体系中,如果基类的构造函数调用了基类的virtual函数,则当产生派生类对象时,会首先调用基类构造函数,此时调用了基类版本的virtual函数,不是派生类的版本。基类构造期间virtual函数不会下降到派生类阶层。取而代之的是对象的作为就像隶属基类类型一样。换句话说:在基类构造期间,virtual函数不是virtual函数。
在派生类对象的基类构造期间,对象的类型是基类而不是派生类。

总结

在构造和析构期间不要调用virtual函数,因为这类调用从不下降至派生类那层(比起当前执行构造函数和析构函数那层)

10.令operator=返回一个reference to *this

概要

为了实现连锁赋值,赋值操作符必须返回一个reference指向操作符的左侧实参。

1
2
3
4
5
6
7
8
9
class widget{
public:
...
widget& operator=(const widget& rhs){ //返回类型是一个reference,指向当前对象
...
return *this; //返回左侧对象
}
...
};

总结

令赋值(assignment)操作符返回一个reference to *this