虚析构函数必须显式声明为virtual,否则通过基类指针删除派生类对象时仅调用基类析构函数,导致派生类资源未释放而泄漏;正确写法是public virtual ~Base() = default。

虚析构函数必须显式声明为 virtual
基类的析构函数如果不加 virtual,当通过基类指针删除派生类对象时,只会调用基类析构函数,派生类部分不会被清理——这是典型的资源泄漏源头。
正确写法是:在基类中把析构函数声明为 virtual,且通常设为 public(除非你明确需要受控销毁):
class Base {
public:
virtual ~Base() = default; // 推荐:= default 语义清晰,且能被编译器优化
};
- 不能只靠继承关系“隐式变成虚”,必须显式写
virtual - 即使函数体为空,也别省略
virtual关键字;virtual ~Base() {}也合法,但不如= default明确 - 派生类析构函数自动成为虚函数,无需再写
virtual(写了也不报错,但冗余)
什么时候必须加虚析构函数
只要类设计为多态基类——即预期被继承、且用户会通过基类指针或引用管理派生类对象生命周期——就必须加 virtual 析构函数。
典型场景包括:
- 类里已有至少一个
virtual函数(比如virtual void draw()) - 类被设计成接口(如
Shape、Logger、Handler) - 你往
std::vector<:unique_ptr>>或std::shared_ptr里存了派生类对象
反例:纯数据类(struct Point { int x, y; };)、不打算被继承的工具类,不需要虚析构函数,加了反而增加虚表开销。
virtual ~Base() = default 和 virtual ~Base() {} 的区别
二者都能实现虚析构,但语义和行为有细微差别:
-
= default表示“请编译器生成默认行为”,它保留了类的平凡析构(trivial destructor)属性(如果成员都满足条件),有利于std::is_trivially_destructible_v为true,对某些泛型代码或内存布局敏感场景重要 -
{}是用户提供的定义,哪怕空,也会让类型变为非平凡析构(non-trivial),可能影响std::vector的移动/销毁策略 - 若类含非平凡成员(如
std::string、自定义析构函数的成员),两者实际生成的代码几乎一样,选= default更一致
容易忽略的坑:虚析构函数不能是 protected 或 private(除非有意禁止直接 delete)
如果你把虚析构函数设为 protected:
class Base {
protected:
virtual ~Base() = default;
};
那么 delete ptr;(其中 ptr 是 Base*)会编译失败——因为析构函数不可访问。这在某些 RAII 封装中是故意设计(强制走 close() 或 shutdown() 接口),但绝大多数多态基类应保持 public。
另一个常见疏漏:模板基类是否需要虚析构?答案是——看使用方式。如果模板类本身不被直接用作多态基类(比如 template),一般不需要;但如果特化后被当作接口(如 Container 被继承并用指针管理),那就得按需加。
虚析构函数不是语法糖,它是对象销毁路径上唯一能保证完整链路执行的机制。漏掉它,程序可能跑得通、测试也过,直到某次析构顺序变化或资源释放时机偏移,才突然崩溃或泄漏——这种问题最难复现。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











