基类指针 delete 派生类对象会跳过派生类析构函数,因为析构调用是静态绑定,只看指针类型;虚析构函数通过动态绑定确保按继承链依次调用 Derived::~Derived() 和 Base::~Base()。
当 Base* 指向一个 Derived 对象,且 Base::~Base() 不是虚函数时,delete ptr 只会调用 Base::~Base(),完全不触发 Derived::~Derived()。这不是“内存泄漏”的典型表现(堆内存本身会被释放),而是**资源泄漏**:派生类中申请的资源(如 new 的内存、打开的文件句柄、锁、GPU 显存等)无法被清理。
根本原因是 C++ 的析构调用是静态绑定的——编译器只看指针类型(Base*),不查实际对象类型。
把 Base::~Base() 声明为 virtual 后,析构调用变成动态绑定。运行时根据实际对象类型,从虚表中找到完整的析构链:Derived::~Derived() → Base::~Base(),确保所有层级的清理逻辑都执行。
关键点:
virtual(否则容易误用)virtual ~Base() = 0;),但必须提供定义(哪怕空实现)std::string、std::vector),则不需要虚析构class Base {
publi
c:
virtual ~Base() = default; // ✅ 推荐:default 实现简洁且内联友好
};
class Derived : public Base {
int* data;
public:
Derived() : data(new int[100]) {}
~Derived() override { delete[] data; } // ✅ 会被正确调用
};
现象不是“程序崩溃”或“报错”,而是**静默失效**:
Too many open files)Derived::~Derived() 的断点完全不命中虚析构本身开销极小(一次虚表查表 + 函数调用),远小于它防止的资源泄漏代价。但要注意:
memcpy、memset 等低级操作的安全性虚析构不是“写了就安全”,而是“不写就一定危险”——只要存在多态删除场景,就必须有。最容易被忽略的是:即使你没写 delete,第三方库(如 std::unique_ptr)也可能替你删。