虚函数在构造函数中调用的是当前正在构造的类的版本,而非派生类重写版本;因虚表指针此时指向当前类虚表,动态类型尚未提升至派生类。

虚函数在构造函数里调用时,实际执行的是哪个版本?
调用的是当前正在构造的那个类的版本,不是最终派生类的重写版本。因为此时派生类的成员尚未初始化,虚表指针(vptr)指向的是当前类的虚表——C++ 标准明确要求:对象的动态类型在构造过程中是“逐步提升”的,从基类开始,每完成一层构造,动态类型才向上推进一级。
常见错误现象:Base::Base() 中调用 virtual void foo(),即使派生类 Derived 重写了它,也只会调用 Base::foo(),而不是 Derived::foo();调试时断点进不去重写函数,容易误以为虚函数没生效。
- 使用场景:想在基类构造中“预留钩子”,让派生类定制初始化逻辑(比如注册、预分配资源)
- 这不是编译错误,也不报 warning,但行为与直觉严重不符
- 参数差异无关紧要——哪怕
foo(int)和foo(double)都存在,决议仍按当前类的虚表静态绑定
为什么不能靠“延迟调用”绕过这个问题?
有人会想:在构造函数末尾启动一个 std::thread 或发消息到事件循环里去调用虚函数。这依然危险——线程可能在 Derived 构造完成前就执行,访问未初始化的成员变量,触发未定义行为。
性能 / 兼容性影响:这类“异步补救”不仅没解决根本问题,还引入竞态、生命周期管理复杂度,且无法跨平台保证顺序(比如 Windows 的 PostMessage 和 Linux 的 epoll 回调时机不同)。
立即学习“C++免费学习笔记(深入)”;
- 不要在构造函数里启动任何可能调用
this上虚函数的异步操作 -
std::shared_ptr+enable_shared_from_this也不能救——shared_from_this()在构造函数里调用是未定义行为 - 如果真需要后期绑定,改用工厂函数返回已完全构造的对象,再调用虚函数
安全替代方案:两阶段初始化 or 模板策略
最直接的办法是把“需要多态行为”的逻辑拆出来,不在构造函数里做。比如提供一个 init() 成员函数,由用户显式调用;或者用模板参数把策略编译期固定,避开虚调用。
示例对比:
// ❌ 危险
struct Base {
Base() { on_ready(); } // 调用的是 Base::on_ready()
virtual void on_ready() {}
};
// ✅ 安全:两阶段
struct Base {
Base() = default;
void init() { on_ready(); } // 此时对象已完整构造
virtual void on_ready() {}
};
// ✅ 安全:模板策略(无虚表开销)
template<typename Policy>
struct BaseT {
BaseT() { Policy::on_ready(*this); }
};
- 两阶段方案要注意:用户必须记得调
obj.init(),否则逻辑不执行——可配合std::optional或标记位做运行时检查 - 模板策略适合策略固定、编译期可知的场景,但会增加实例化膨胀
- 纯虚函数在构造中调用更危险:
pure virtual function called运行时报错,不是崩溃就是 abort









