贝利信息

C++中的析构函数为什么要写成虚函数?(防止基类指针删除时内存泄漏)

日期:2026-01-23 00:00 / 作者:冰火之心
基类指针 delete 派生类对象会跳过派生类析构函数,因为析构调用是静态绑定,只看指针类型;虚析构函数通过动态绑定确保按继承链依次调用 Derived::~Derived() 和 Base::~Base()。

为什么基类指针 delete 派生类对象会跳过派生类析构函数?

Base* 指向一个 Derived 对象,且 Base::~Base() 不是虚函数时,delete ptr 只会调用 Base::~Base(),完全不触发 Derived::~Derived()。这不是“内存泄漏”的典型表现(堆内存本身会被释放),而是**资源泄漏**:派生类中申请的资源(如 new 的内存、打开的文件句柄、锁、GPU 显存等)无法被清理。

根本原因是 C++ 的析构调用是静态绑定的——编译器只看指针类型(Base*),不查实际对象类型。

虚析构函数如何解决这个问题?

Base::~Base() 声明为 virtual 后,析构调用变成动态绑定。运行时根据实际对象类型,从虚表中找到完整的析构链:Derived::~Derived()Base::~Base(),确保所有层级的清理逻辑都执行。

关键点:

class Base {
public:
    virtual ~Base() = default; // ✅ 推荐:default 实现简洁且内联友好
};

class Derived : public Base { int* data; public: Derived() : data(new int[100]) {} ~Derived() override { delete[] data; } // ✅ 会被正确调用 };

不写虚析构函数的常见错误现象

现象不是“程序崩溃”或“报错”,而是**静默失效**:

虚析构函数的性能与 ABI 影响

虚析构本身开销极小(一次虚表查表 + 函数调用),远小于它防止的资源泄漏代价。但要注意:

虚析构不是“写了就安全”,而是“不写就一定危险”——只要存在多态删除场景,就必须有。最容易被忽略的是:即使你没写 delete,第三方库(如 std::unique_ptr)也可能替你删。