必须为基类析构函数声明virtual,否则通过base*删除derived对象时会跳过子类析构,导致资源泄漏;仅当基类不被多态删除、禁止继承、或用于编译期多态等场景才可省略。

不写虚析构函数时,delete 父类指针会漏掉子类析构逻辑
当用 Base* 指向一个 Derived 对象,并通过该指针调用 delete 时,若 Base::~Base() 不是虚函数,C++ 只会调用 Base 的析构函数,完全跳过 Derived::~Derived()。这意味着子类中申请的资源(如堆内存、文件句柄、socket 连接等)不会被释放。
常见错误现象:valgrind 报告内存泄漏;程序运行一段时间后崩溃或卡死;调试时发现对象内部指针没被置空、资源未关闭。
- 只在基类需要被多态删除时才必须加
virtual—— 如果类不作为接口、不被继承、或从不通过基类指针 delete,就不需要 - 即使析构函数函数体为空,也得显式声明为
virtual ~Base() = default;或virtual ~Base() {} - 纯虚析构函数也要提供定义(哪怕空实现),否则链接失败:
virtual ~Base() = 0;后必须在 .cpp 中写Base::~Base() {}
虚析构函数不会影响性能,但能避免未定义行为
虚函数表指针(vptr)开销只在对象创建时发生一次,析构本身不比非虚版本慢。真正代价是:没加 virtual 导致的未定义行为(UB)—— C++ 标准不保证任何结果,可能表现为“看似正常”,也可能在优化级别升高后突然出错。
典型陷阱:
立即学习“C++免费学习笔记(深入)”;
- 使用
std::unique_ptr<base>管理派生类对象,若Base析构非虚,unique_ptr的默认删除器仍只会调用Base::~Base() - 工厂函数返回
std::shared_ptr<base>,同样依赖基类析构是否为虚来决定资源清理完整性 - RAII 类型(如自定义锁、临时文件管理器)若被继承且析构非虚,其自动清理机制就失效了
哪些情况可以不写虚析构函数?
不是所有基类都需要虚析构。关键判断依据是:有没有可能通过基类指针(或智能指针)去销毁派生类对象。
- 类设计为 final(
class Base final),明确禁止继承 → 不需要 - 类仅作类型抽象(如 tag dispatch 用的空结构体),不含资源管理逻辑 → 可省略
- 继承关系仅用于编译期多态(模板特化、CRTP),运行时无
Base*指向Derived对象 → 不需要 - 派生类对象总是栈上创建、或由专用删除器管理(如自定义
deleter传给unique_ptr)→ 基类析构可不虚,但需确保删除路径可控
现代 C++ 中更安全的替代方案
虚析构解决的是“通过基类接口销毁派生对象”的问题,但如果你能避开这个模式,就能绕过风险。比如:
- 用模板替代继承:把算法逻辑写成函数模板,接受任意满足接口的类型,而非继承同一基类
- 用
std::variant或std::any替代运行时多态,配合std::visit显式分发 - 若必须多态,优先用
std::shared_ptr并配合自定义删除器(在构造时绑定具体类型析构逻辑),但这增加了复杂度,不如直接加virtual简洁
虚析构看起来只是加个关键字,但它锁定了整个继承体系的资源生命周期语义。漏掉它,往往要等到压测或上线后才暴露,而且很难复现。








