Effective C++ 05 C++析构函数 01 木灵的炼金工作室

这是Effective C++的学习记录。

条款07:为多态基类声明virtual析构函数

虚函数是C++多态和泛型的基础,它使用动态联编来实现函数的多态。
所谓动态联编,就是函数的调用不是在编译时就能决定的。比如程序调用基类的虚成员,程序可能需要在运行时才能得知它究竟要调用的是基类成员还是衍生类成员。

虚函数技术使用虚函数表来实现:在每一个含有虚成员的实例的内存的头部,C++编译器为其生成一个二维的函数指针数组,每一个虚函数拥有其中的一个一维函数指针数组,其中存放了调用这一虚函数所需的所有衍生函数的地址。

比如对于下面的程序

class base {
    virtual void func1() { cout << "base"; }
    void func2() {}
};

class deri : public base {
    void func1() override { cout << "deri"; }
    virtual func3() {}
};

base的虚函数表内容就是

vptr
[0]    func1
[0][0] base::func1

而deri的虚函数表内容则是。注意:你可以认为这个表是对于基类生效的。

vptr
[0]    func1
[0][0] deri::func1
[1]    func3
[1][0] deri::func3

此时当我们调用基类的func1方法时,程序会根据虚函数表找到deri::func1实现。

对于析构函数,情况也是相似的。对于一个派生类:调用析构函数有这么两种情况:

  1. 直接调用派生类的析构函数:此时程序会递归调用其所有父类的析构函数,内存不会泄漏
  2. 使用函数指针仅调用基类的析构函数:此时程序不会自动调用派生类的析构函数,有泄漏风险,因此需要通过虚函数技术来阻止这样的风险

因此我们为基类声明一个virtual析构函数,使用内联的虚函数表来提示程序:“你该调用的是派生类的析构函数”,从而将情况2转化为情况1。

如果没有声明virtual析构函数,在以基类指针调用派生类析构函数时,程序会将派生类中的基类部分释放,从而形成一个不完全销毁的实例,引发内存泄漏。
所以,不要将任何没有virtual析构函数的类当作基类。

条款08:析构函数不应释放异常

所有过程都有可能产生异常。但是,C++不建议析构函数抛出异常。
此处的抛出是指“析构函数产生异常且未被析构函数内部的catch块捕获”

原因是这样的:假设有一个由对象实例构成的数组或者std::vector,现在我希望将这个数组的空间释放掉。如果析构函数抛出了异常且未被析构函数处理,那么异常会传播至main过程。但目前还不是最危险的时候。如果程序在存在异常的情况下继续运行,如果接下来的析构又抛出了异常,那么就会产生“两个异常同时存在”的未定义情况,此时程序必须强制停止,否则会出现可怕的结果,而强制终止程序也会存在一定风险。

为了解决这一问题,比较好的做法是将容易抛出异常的过程单独封装为一个方法或放入try块中,然后使用catch块将其扼杀在实例内部。


Copyright AmachiInori 2017-2021. All Right Reserved.
Powered By Jekyll.
amachi.com.cn