虚析构函数必须声明为virtual,否则通过基类指针delete子类对象时子类析构函数不会被调用,导致资源泄漏;只要存在多态删除场景(如工厂返回指针、容器存储基类指针等),就必须加;基类声明virtual析构后,派生类析构自动为虚,建议显式加override。

虚析构函数不加,delete 父类指针时子类析构函数根本不会调用
这是最直接的后果。当用 Base* 指向一个 Derived 对象,且 Base::~Base() 不是 virtual 时,delete ptr 只会调用 Base::~Base(),Derived::~Derived() 完全被跳过。如果子类在析构里释放文件句柄、内存、网络连接或调用 close()/delete,这些操作就彻底漏掉了。
常见错误现象:
- 程序运行时不报错,但反复创建/销毁对象后出现句柄耗尽(
Too many open files) - Valgrind 报告“still reachable”内存块,实际是子类分配的堆内存没被
delete - 资源泄漏只在子类有非 trivial 析构逻辑时才暴露,基类空析构会让问题潜伏很久
什么时候必须声明为 virtual?看多态删除是否可能发生
不是“只要用了继承就要加”,而是“只要存在通过基类指针/引用创建子类对象,并可能用基类指针 delete 它”的场景,就必须加。典型包括:
- 工厂函数返回
std::unique_ptr或裸指针 - 容器存的是
std::vector<:unique_ptr>> - 回调系统中注册了子类对象,由框架统一销毁
反例:子类只用于栈上对象(Derived d;),或完全不通过基类指针销毁,则虚析构不是必需的——但加了也没坏处,且容易误判使用方式,所以只要类设计为被继承,就应默认加 virtual。
立即学习“C++免费学习笔记(深入)”;
virtual 析构函数对性能和 ABI 的影响其实极小
有人担心虚函数表开销或调用成本,但现实是:
- 析构函数本身只调用一次,且通常在对象生命周期末尾,性能敏感度远低于频繁调用的成员函数
- 编译器能优化掉部分虚调用(如静态类型已知时),但无法保证所有场景
- ABI 影响仅限于增加一个虚函数表项,不影响对象布局(析构函数不改变
sizeof) - 现代 C++ 中,若基类已有其他虚函数(比如
virtual void foo() = 0;),加virtual析构几乎零成本
真正代价是没加带来的不确定性——你永远不知道下游用户会不会拿你的类做多态删除。
正确写法:基类析构声明为 virtual,子类无需显式加 virtual
只要基类析构是 virtual,派生类的析构自动成为虚函数,即使不写 virtual 关键字。但建议显式写出,提高可读性:
class Base {
public:
virtual ~Base() = default; // ✅ 推荐:= default 更清晰
};
class Derived : public Base {
public:
virtual ~Derived() override { / 清理子类资源 / } // ✅ 显式 + override
};
注意:= default 和 {} 都可以,但避免写成 ~Base() {} —— 这会抑制编译器生成移动操作,且语义不如 = default 明确。
最容易被忽略的一点:纯虚析构函数必须提供定义,哪怕为空:
class Interface {
public:
virtual ~Interface() = 0; // 声明纯虚
};
Interface::~Interface() = default; // ✅ 必须定义,否则链接失败虚析构函数不是“最佳实践”的点缀,而是多态对象生命周期管理的契约底线——漏掉它,等于把资源释放的控制权交给了未定义行为。









