02 September 2022

C++ 核心指南目录

C.35: A base class destructor should be either public and virtual, or protected and non-virtual

理由

为了避免未定义的行为。如果析构函数为 public ,则调用处的代码就可以通过基类对象的指针释放继承类对象。如果基类的析构函数不是 virtual ,这样操作的结果是未定义的。

如果基类的析构函数添加了 protected 修饰符,则调用处的代码无法通过继承类对象的基类的指针指向的析构函数销毁释放该对象。此时,基类的析构函数不必是 virtual 的,但必须是 protected ,不能是 private 。因为继承类中可以在其析构函数内调用基类的析构函数。

一般来说,编写基类的程序员不需要知道析构过程中需要哪些具体操作,继承类必须处理好析构过程。

例子

struct Base { // BAD: implicitly has a public non-virtual destructor
    virtual void f();
    // 此处有一个隐藏的 public 且非 virtual 的析构函数
};

struct D : Base {
    string s {"a resource needing cleanup"};
    ~D() { /* ... do some cleanup ... */ }
    // ...
};

void use()
{
    unique_ptr<Base> p = make_unique<D>();
    // ...
} // p's destruction calls ~Base(), not ~D(),
  // which leaks D::s and possibly more

注意

虚函数定义了一个接口,使用的时候,只需关注接口就可以了,不需要了解继承类的具体实现。如果析构函数是基类的接口,那么就要允许安全的进行释放销毁操作。

注意

析构函数不能为 private ,不然会导致该对象无法使用:

class X {
    ~X();   // private destructor
    // ...
};

void use()
{
    X a;                        // error: cannot destroy
    auto p = make_unique<X>();  // error: cannot destroy
}

例外

以下情况你可能需要protected virtual析构函数:一个继承的对象可以通过基类指针释放另一对象。但是很少使用。

强化

  • 一个类如果有虚函数,则必须定义 publicvirtual 的析构函数,或者 protected 且非 virtual 的析构函数。
  • 如果类公开继承了一个基类,则基类必须有 publicvirtual ,或 protected 且非 virtual 的析构函数。