析构函数中调用虚函数会绑定到当前类版本,因vptr已指向基类虚表且派生类成员已销毁;若该虚函数为纯虚则触发pure virtual function call崩溃。

析构函数里调用 virtual 函数会绑定到基类版本
析构过程是“从派生类往基类退化”的:先执行 Derived::~Derived(),再把对象的 vptr 指向基类虚表,最后才调用 Base::~Base()。此时派生类成员早已销毁,vptr 也不再指向派生类虚表。哪怕你在 Base::~Base() 里写 log(),它也只会调用 Base::log() —— 即使该函数在 Derived 中被重写为虚函数。
pure virtual function call 崩溃直接发生在这里
如果 Base::log() 是纯虚函数(virtual void log() = 0;),而你又在 Base::~Base() 里调用了它,程序会在运行时崩溃,报错 pure virtual function call。这不是编译错误,而是链接后、运行中触发的未定义行为。
- 常见诱因:基类析构函数中调用了一个虚函数,而该函数在某个派生类中被重声明为纯虚(哪怕只在一个分支里)
- 调试困难:堆栈可能只显示
__cxa_pure_virtual,看不出具体哪一行触发 - 不是所有编译器都立即崩溃——有些会静默跳过,导致资源泄漏更隐蔽
纯虚析构函数本身必须有实现,但不等于能安全调用其他纯虚函数
很多人混淆两个概念:virtual ~Base() = 0; 是合法且常见的(用于强制抽象类),但它必须提供定义(如 Base::~Base() { })。但这只是解决了“析构链能走通”的问题,并不意味着你可以在析构函数体里随意调用别的纯虚函数。
- 纯虚析构函数的定义是链接必需项,和“能否在析构中调用虚函数”无关
- 即使你给
log()加了默认实现(virtual void log() { }),在析构中调用它仍可能访问已销毁的成员变量 - 真正安全的做法是:析构函数只做清理,不依赖多态逻辑;差异化行为移到独立的
teardown()或close()函数中,在delete前手动调用
多重继承下风险被放大
在多重继承结构中,析构顺序更复杂,虚基类由最派生类统一析构。如果你在某个中间基类的析构函数中调用虚函数,而该函数在另一条继承路径上是纯虚的,就可能触发崩溃,且调用链难以追踪。
- 典型场景:接口类 A 和 B 都声明了
virtual void cleanup() = 0;,C 同时继承 A 和 B,但只实现了 A 的cleanup,忘了 B 的 - 当通过 B* 删除 C 对象时,B 的析构函数调用
cleanup(),就会 crash - 这种问题在模板或泛型容器中尤其难复现,因为指针类型可能由类型推导隐式生成
析构阶段的对象状态比构造阶段更脆弱——成员变量可能已被销毁,虚表指针已重置,连 this 指针都不可靠。任何依赖多态的调用,本质上都是在踩边界。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











