基类析构函数必须是virtual,否则通过基类指针删除派生类对象时,派生类析构函数不会被调用,导致资源泄漏;只要类可能被多态使用,就应声明为virtual,推荐用virtual ~Base() = default;。

为什么基类析构函数必须是 virtual
不加 virtual 的基类析构函数,会导致派生类对象通过基类指针删除时,派生类部分的析构函数根本不会被调用——这是内存泄漏和资源未释放的典型根源。
常见错误现象:delete ptr; 后,派生类中申请的堆内存、打开的文件、持有的互斥锁等完全没被清理,但程序不崩溃,问题难定位。
只要存在多态使用(即用基类指针/引用指向派生类对象,并可能通过该指针删除对象),基类析构函数就必须声明为 virtual。这不是可选项,是强制约定。
虚析构函数的写法和默认行为
虚析构函数可以是纯虚的,也可以有实现;但除非你明确想阻止实例化基类,否则更推荐提供空实现,而非 = 0。
立即学习“C++免费学习笔记(深入)”;
-
virtual ~Base() = default;:C++11 起最简洁安全的写法,编译器生成默认析构逻辑,且保持虚性 -
virtual ~Base() {}:显式空实现,语义清晰,兼容老标准 -
virtual ~Base() = 0;:纯虚析构函数,此时**必须在类外提供定义**(哪怕空实现),否则链接失败 —— 这是常被忽略的坑
示例:
struct Base {
virtual ~Base() = 0;
};
Base::~Base() = default; // 必须有这一行,否则链接时报错 undefined reference to `Base::~Base()`
什么时候可以不写虚析构函数
仅当确定该类**绝不会作为多态基类被继承并用基类指针删除对象**时,才可省略 virtual。
典型安全场景:
- 类设计为 final:
struct FinalClass final { ~FinalClass(); };,编译器禁止继承,自然无需虚析构 - 类只用于值语义(如
std::string、std::vector),从不通过指针/引用多态使用 - 类是工具类或命名空间替代(如
namespace math { ... }),根本不允许实例化
但只要头文件公开、类名可被继承,就应默认加上 virtual —— 保守比修复容易得多。
虚析构与 move/copy 构造函数的关系
虚析构函数本身不影响移动或拷贝语义,但它会间接影响编译器是否自动生成移动操作。
关键点:
- 一旦类声明了任何用户定义的析构函数(包括
virtual ~T() {}),编译器就**不再自动生成移动构造函数和移动赋值运算符**(C++11/14 行为) - 若需移动语义,必须显式声明
T(T&&) = default;或自行实现 -
virtual ~T() = default;是特例:它不阻止移动操作的自动生成(C++17 起更明确支持)
所以,优先用 = default,既保虚性,又不意外禁用移动语义。
虚析构真正麻烦的地方不在写法,而在于“不确定要不要加”——只要类有被继承的可能,就加;加了之后,记得检查移动语义是否还在;如果用了 = 0,千万别忘了类外定义。


















