为何接口能关联非虚类重写?解析C++类族虚限定符传播机制
这是个非常典型的C++虚函数细节问题,刚好能帮你理清虚函数的继承传播规则——这也是很多开发者容易踩坑的点。
先从你熟悉的场景1说起:
场景1:定义包含非虚函数func()的父类Parent,子类Child重写该函数,通过Parent指针调用时,执行的是父类的func()而非子类的。
对应的代码:
#include <iostream> using namespace std; struct Parent { void func() { cout << "Parent!" << endl; } }; struct Child : Parent { void func() { cout << "Child!" << endl; } }; int main() { Parent* pc = new Child; pc->func(); // 输出 "Parent!" }
这个逻辑没问题,因为Parent::func()没有被声明为virtual,C++会采用静态绑定:编译期就根据指针的静态类型(Parent*)确定调用Parent::func(),完全不管指针实际指向的对象类型是Child。
接下来重点拆解场景2的核心原因:
场景2:为Parent添加继承自纯虚接口Interface,此时Parent的func()仍未加virtual,但通过Parent指针调用时,却执行了Child的func()。
对应的代码:
#include <iostream> using namespace std; struct Interface { virtual void func() = 0; }; struct Parent : Interface { void func() { cout << "Parent!" << endl; } }; struct Child : Parent { void func() { cout << "Child!" << endl; } }; int main() { Parent* pc = new Child; pc->func(); // 输出 "Child!" }
你这里的关键误解是:Parent的func()其实已经是虚函数了,哪怕你没写virtual关键字。
C++有个非常重要的规则:一旦某个函数在继承链的某个基类中被声明为virtual(包括纯虚函数virtual ... = 0),那么所有派生类中,只要函数签名(函数名、参数列表、const属性等)完全匹配,这个函数就自动继承virtual属性,不需要显式写virtual关键字。
放到你的场景里就是:
Interface::func()是纯虚函数,本质是virtual的;Parent继承Interface后实现了func(),这个函数自动成为virtual函数——哪怕你没写virtual;Child继承Parent并重写func(),同样自动继承virtual属性。
当你用Parent* pc = new Child;调用pc->func()时,因为func()是虚函数,C++会触发动态绑定:运行时根据指针实际指向的对象类型(Child),调用Child::func(),这就是为什么输出是Child!。
另外你提到的构造顺序其实和这个调用行为无关——当你执行pc->func()时,Child对象已经完全构造完成,虚函数表(vtable)已经正确初始化,指向Child类的虚函数入口。
最后再提炼一下这个容易被忽略的规则:
- 虚函数的
virtual属性是向下传播的,只要继承链顶端的基类声明了virtual,后续所有派生类的同名同签名函数都是虚函数; - 显式写
virtual只是让代码更清晰易读,不是语法上必须的(不过实际开发中非常推荐显式标注,避免后续维护时产生误解)。
内容的提问来源于stack exchange,提问作者VincentDM

