必须将基类析构函数声明为virtual,否则通过基类指针delete派生类对象时仅调用基类析构函数,跳过派生类中释放动态资源的逻辑,导致内存等资源泄漏;虚析构需显式声明且必须有定义,含其他虚函数的基类几乎必然需要虚析构。

只要基类可能被用作多态接口(即通过基类指针或引用管理派生类对象),就必须把析构函数声明为 virtual,否则 delete 基类指针时,派生类的析构函数根本不会执行,动态资源必然泄漏。
为什么非虚析构函数会导致内存泄漏
核心问题不是“没调用析构函数”,而是只调用了基类的析构函数,跳过了派生类中负责释放资源的逻辑。比如 new 出来的堆内存、打开的文件句柄、申请的 GPU 显存等,全靠派生类析构函数清理。
- 现象:
delete base_ptr;输出只有基类析构日志,派生类析构函数完全静默 - 本质:编译器静态绑定,不查虚函数表,直接按指针类型(
Base*)调用~Base() - 后果:派生类成员变量(如
int* data)变成悬空指针,对应内存永久丢失
虚析构函数怎么写才有效
必须在基类中显式加 virtual,且不能省略实现(哪怕是空的)。派生类析构函数无需加 virtual 或 override —— 编译器自动继承虚性。
- 正确写法:
virtual ~Base() = default;或virtual ~Base() { /* 清理基类资源 */ } - 错误写法:
~Base() {}(非虚)、virtual ~Base() = 0;(纯虚但没定义体 → 链接失败) - 纯虚析构函数要两步:声明为
= 0+ 单独定义函数体,例如:class Base { public: virtual ~Base() = 0; };Base::~Base() {}
哪些类必须加虚析构函数
不是所有基类都需要,关键看是否参与多态销毁。典型场景包括:抽象接口类、框架中供用户继承的基类、容器里存基类指针的类。
- 需要:
class Drawable { virtual void draw() = 0; virtual ~Drawable() = 0; }; - 不需要:
class Vec3 { double x,y,z; ~Vec3() = default; };(纯数据聚合,没人用Vec3*指向Vec4对象) - 警告信号:类里已有其他虚函数(如
virtual void init())→ 析构函数几乎一定得是虚的
容易被忽略的坑
虚析构函数解决的是“调不调得到”的问题,但它不解决“调得对不对”的问题。很多泄漏其实发生在析构函数内部逻辑错误,而非虚性缺失。
- 派生类析构函数里忘了
delete[]而用了delete→ 未定义行为,可能 crash 或静默泄漏 - 基类析构函数里调用了虚函数(包括
this->foo())→ 此时对象已开始析构,虚函数表已被部分销毁,行为未定义 - 用智能指针(如
std::unique_ptr<base>)也不能绕过虚析构要求:若Base析构非虚,unique_ptr删除时照样只调基类析构
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











