构造函数中调用纯虚函数必崩,因对象vptr此时指向基类虚表,而纯虚函数条目被编译器设为指向__cxa_pure_virtual崩溃入口;该行为是标准明确定义的,非未定义行为,clang/gcc可静态检测但需显式启用检查。

会直接崩溃,抛出 pure virtual function call 运行时错误。
为什么构造函数里调纯虚函数必崩
纯虚函数在基类中没有实现(声明为 virtual void f() = 0;),它的虚函数表条目被编译器设为指向一个“兜底崩溃函数”。构造开始时,对象的 vptr 指向基类虚表,此时若调用该函数,就会跳进那个崩溃入口。
- 不是未定义行为 —— 是明确定义的崩溃路径
- 不依赖编译器或平台,所有符合标准的实现都如此
- 哪怕派生类已提供实现,构造基类阶段也完全不可见
常见触发场景不止“直接调用”
容易被忽略的是间接调用:基类构造函数调用一个虚函数,而这个虚函数在派生类中被重写为纯虚函数,同样会崩。
-
Base构造函数里调init() -
Derived把init()声明为virtual void init() = 0; - 创建
Derived对象 →Base::Base()执行 →init()解析到Base表中的纯虚条目 → 崩溃
Clang 和 GCC 都能静态检测,但默认不报
像 Clang 的 optin.cplusplus.VirtualCall 检查项能发现这类问题,但需显式启用(如 -Wweak-vtables -Wexit-time-destructors 组合不够,要加 -Wpure-virtual-call 或启用相应 analyzer)。
- MSVC 在调试模式下可能输出更明确的断言信息
- Release 模式下通常静默崩溃,无堆栈、无日志,极难定位
- 建议在 CI 中加入
-Werror=undefined+ 启用虚函数调用检查
修复的关键是切断“构造期多态依赖”
不能靠“保证派生类不重写为纯虚”来规避 —— 设计上就该杜绝这种耦合。
- 把初始化逻辑拆出来,用两阶段方式:先构造,再调
initialize() - 用非虚接口模式(NVI):公有非虚
init()调私有虚do_init(),确保调用发生在构造完成之后 - 若必须参数化行为,改用构造函数参数传递(如
Base(InitMode mode)),而非虚函数分发
最危险的不是写错,而是运行时没报错——直到某次派生类改了个函数声明,它才突然崩在上线前五分钟。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











