29
根本原因一句话
执行父类构造函数期间,对象的 vptr 指向【父类虚表】,尚未更新为子类虚表;动态绑定依赖vptr,因此只能调用父类版本,无法触发子类重写函数。
完整时序(Base <- Derived)
Derived d;
-
开辟内存(原始内存,无有效vptr)
-
调用 Base::Base()
✅ 编译器隐式代码:设置 vptr = Base 的虚表
✅ 运行 Base 构造函数体,此时在构造内调用虚函数 → 查询Base虚表,执行Base版本 -
Base构造完全结束
-
调用 Derived::Derived()
✅ 编译器隐式代码:覆盖 vptr = Derived 的虚表
✅ 运行 Derived 构造函数体,此时调用虚函数才会走Derived版本
两个关键本质概念
-
多态(动态绑定)的前提
通过vptr查找虚函数;vptr存着当前对象“类型标签”。 -
对象是逐步构造出来的
C++标准视角:
在派生类构造函数开始执行前,这个对象还只是一个基类对象,派生部分还未初始化,派生类成员变量完全未构造。
C++标准直接规定语义:
当处于基类构造/析构函数中,将对象视作基类类型实例,动态绑定解析到基类。
就算底层不谈vptr,从标准设计逻辑上也禁止多态。
一个极其重要的安全逻辑(设计层面的根源)
假设允许在父类构造里调用子类虚函数:
子类函数可能访问子类的成员变量,但是!子类成员还没有构造,内存未初始化,直接访问 = 未定义行为、崩溃。
C++委员会为了规避毁灭性的未定义行为,做出两条约束:
-
构造、析构过程中,vptr阶段性切换;
-
此时虚函数动态绑定锁定为当前正在构造/析构的类。
底层实现(vptr切换)是实现手段;
防止访问未构造的派生类成员,避免UB 才是语言设计的根本动机。
配套:析构函数一模一样的逻辑
virtual ~Base()
{
func(); // 只会调用Base::func
}
析构顺序:
先执行Derived析构(vptr=Derived)→ 然后切换vptr=Base → 执行Base析构。
Base析构内部vptr已经切回基类,不会调用子类函数。
面试标准分层回答(直接背)
-
底层实现角度
继承构造时,先执行基类构造,此时vptr指向基类虚表,尚未更新为派生类虚表;虚函数依靠vptr动态绑定,因此只能调用基类版本。 -
C++语言设计根本原因(加分项,面试官最爱听)
派生类对象是分步构造的,基类构造执行阶段,派生类所有成员还未初始化。
如果此时允许调用派生类虚函数,函数内部极有可能读写派生类未初始化的成员,引发未定义行为。
为保证程序安全,C++标准规定:在基类构造/析构期间,对象视为基类实例,虚函数不会触发派生类实现。
高频延伸面试题
问:能不能在构造函数里使用多态?
答:语法层面无法直接实现;工程上采用工厂模式,先构造完成对象,再调用虚函数,保证vptr稳定。
避坑小误区
❌错误说法:“因为对象还没创建完”
不够精准。内存已经分配,但是派生子对象没有构造、vptr还没切换完成。
如果你需要,我提供一段可直接运行的代码,配合注释打印,直观展示vptr变化带来的现象。

浙公网安备 33010602011771号