必须将基类析构函数声明为virtual;否则通过Base*删除Derived对象时子类析构不执行,导致资源泄漏。虚析构确保动态绑定,虽有微小开销但远小于未定义行为风险。

delete 父类指针时子类析构函数不执行?
这是最直接的信号:你用 Base* 指向了 Derived 对象,delete 后子类的清理逻辑(比如释放文件句柄、关闭 socket、delete 成员指针)完全没跑——程序看似正常,实则内存泄漏或资源残留。
根本原因在于:非虚析构函数的调用在编译期就静态绑定了,delete p 只会调用 Base::~Base(),连子类析构函数的符号都懒得查。
- 只要类设计为被继承(哪怕当前没写子类),且有指针/引用通过基类类型管理对象生命周期,就必须把析构函数设为
virtual - 如果类明确禁止继承(C++11 起可用
final修饰类),或者只作工具类(无动态多态使用场景),那可以不加virtual - 纯虚析构函数是合法的:
virtual ~Base() = 0;,但必须在类外提供定义(哪怕空实现),否则链接失败
虚析构函数会影响性能或对象布局吗?
影响极小,但存在。加 virtual 后,类会多一个虚函数表指针(vptr),通常 8 字节(64 位系统),所有对象实例都带这个开销;首次调用虚函数时还有间接跳转的微小延迟。
但和“资源泄漏”“未定义行为”比,这点开销几乎可忽略。真正该警惕的是:为了省这 8 字节,把本该虚的析构函数硬改成非虚——这是用确定的错误去换不确定的优化。
立即学习“C++免费学习笔记(深入)”;
- 虚函数表本身不随对象数量线性增长,只跟类数量有关
- 现代编译器对单个虚函数调用(如析构)常做 devirtualization 优化,实际可能内联
- 如果你的类既不被继承,也不通过基类指针销毁,加
virtual反而增加误导性和二进制体积
基类析构函数是 virtual,子类还需要显式写 virtual 吗?
不需要。C++11 起,子类中同签名的析构函数自动成为虚函数,无论是否加 virtual 关键字。但强烈建议显式加上——既是自我提醒,也避免团队里有人误以为“没写就是非虚”。
更关键的是:子类析构函数若抛异常(比如在清理时 throw),而基类析构函数没声明 noexcept(C++11 默认是 noexcept(true)),会导致程序直接调用 std::terminate()。
- 所有析构函数默认隐式
noexcept,所以子类析构里别throw;真要传播错误,改用返回状态码或日志 - 显式写
virtual ~Derived() noexcept;比只写virtual ~Derived();更清晰 - 如果基类析构是
virtual,但子类忘了写(或删了),编译器不会报错,但语义上仍是虚的——只是可读性差
智能指针(如 std::unique_ptr<base>)能绕过虚析构问题吗?
不能。std::unique_ptr<base> 默认用 delete 销毁对象,它依然依赖 Base 的析构函数是否为虚。如果 Base::~Base() 非虚,照样只调父类析构。
唯一例外是给 unique_ptr 显式传入自定义删除器(deleter),比如 lambda,这时销毁逻辑由你完全控制。但这等于放弃多态语义,退化成手动管理。
-
std::shared_ptr<base>是安全的:它在构造时捕获了原始指针的真实类型,销毁时能正确调用Derived::~Derived(),无论析构是否虚 - 但别因此偷懒——
shared_ptr的类型擦除有额外开销,且掩盖了设计缺陷 - 统一规则最省心:基类可被多态删除 → 析构必须
virtual,不管用什么智能指针
最容易被忽略的点:析构函数的虚性不是看“有没有子类”,而是看“有没有可能通过基类指针销毁子类对象”。只要代码里出现过 Base* p = new Derived; 或类似多态创建+销毁模式,虚析构就是强制契约,不是可选项。








