基类指针 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(),确保所有层级的清理逻辑都执行。
关键点:
立即学习“C++免费学习笔记(深入)”;
- 只要基类有虚函数,就该把析构函数也设为
virtual(否则容易误用) - 虚析构函数可以是纯虚的(
virtual ~Base() = 0;),但必须提供定义(哪怕空实现) - 如果类明确不作为基类使用(比如
std::string、std::vector),则不需要虚析构
class Base {
public:
virtual ~Base() = default; // ✅ 推荐:default 实现简洁且内联友好
};
class Derived : public Base {
int* data;
public:
Derived() : data(new int[100]) {}
~Derived() override { delete[] data; } // ✅ 会被正确调用
};
不写虚析构函数的常见错误现象
现象不是“程序崩溃”或“报错”,而是**静默失效**:
- Valgrind / AddressSanitizer 报告“still reachable”内存块(派生类中 new 的未释放内存)
- 程序长时间运行后出现文件句柄耗尽(
Too many open files) - 自定义资源管理类(如封装 OpenGL texture ID)反复创建却不释放,显存持续增长
- 调试时发现
Derived::~Derived()的断点完全不命中
虚析构函数的性能与 ABI 影响
虚析构本身开销极小(一次虚表查表 + 函数调用),远小于它防止的资源泄漏代价。但要注意:
- 添加虚析构会使类从“POD”变为“non-POD”,影响
memcpy、memset等低级操作的安全性 - 类大小增加(通常多一个虚表指针,8 字节)
- 若基类原本无虚函数,加虚析构会改变 ABI,导致二进制不兼容(尤其在 DLL / SO 场景下需谨慎)
虚析构不是“写了就安全”,而是“不写就一定危险”——只要存在多态删除场景,就必须有。最容易被忽略的是:即使你没写 delete,第三方库(如 std::unique_ptr)也可能替你删。










