4.2. 析构函数:要点回顾

本章重点探讨如何利用析构函数管理资源(特别是内存资源)。鉴于第1章已初步讨论过析构函数,此处先简要回顾这一强大机制的核心要点:

简单示例如下:

#include <iostream>

struct Base {
  ~Base() { std::cout << "~Base()\n"; }
};

struct DerivedA : Base {
  ~DerivedA() { std::cout << "~DerivedA()\n"; }
};

struct VirtBase {
  virtual ~VirtBase() {
    std::cout << "~VirtBase()\n";
  }
};

struct DerivedB : VirtBase {
  ~DerivedB() {
    std::cout << "~DerivedB()\n";
  }
};

int main() {
  {
    Base base;
  }
  {
    DerivedA derivedA;
  }

  std::cout << "----\n";

  Base *pBase = new DerivedA;
  delete pBase; // bad

  VirtBase *pVirtBase = new DerivedB;
  delete pVirtBase; // Ok
}

若运行该代码,将看到 base 调用一次析构函数,而 derivedA 调用两次:先是派生类的析构函数,再是基类的析构函数。这部分代码行为符合预期且正确。

问题出在 pBase 这个 Base* 类型的指针上 —— 指向的是 Base 的派生类对象,但由于 Base 的析构函数非虚,通过基类指针删除派生对象显然违背设计初衷:delete pBase 仅调用 Base::~Base(),而不会调用 DerivedA::~DerivedA()。使用 pVirtBase 时则无此问题,因为 VirtBase::~VirtBase() 声明为虚函数。

当然,C++ 总是为特殊用例留有选择余地 —— 在某些特定场景下(基于充分理由),确实需要通过基类指针(其析构函数非虚)删除派生类对象。

需要注意的是:虚成员函数虽有用,但存在开销。实现会为包含虚函数的类型维护虚函数表,并在每个对象中存储指向该表的指针,这会略微增加对象体积。仅当需要通过基类指针调用派生类析构函数时,才应将析构函数声明为虚函数。

了解这些背景后,再来探讨其与资源管理的关联。