C++基类引用调用重写函数执行派生类版本的原理咨询
C++ 基类引用绑定与虚函数调用原理解析
你已经掌握了函数隐藏、函数重写的基础差异,也能理解指针场景的虚函数行为,以下针对两个核心疑问直接拆解底层规则:
1. 派生类对象绑定基类引用的本质规则
首先要明确值拷贝和引用绑定是完全不同的行为,对应main函数里的两行核心代码:
Parent prt = cld;是值初始化,会发生对象切片:编译器从栈上的Child类型实例cld中,剥离出属于Parent类的成员部分,拷贝生成一个全新的、独立的Parent类型对象prt。这个prt和原对象cld没有任何关联,从构造完成起就是纯粹的Parent实例。Parent &ref_prt = cld;是引用绑定,不会生成任何新对象。C++公有继承的核心语义是is-a:所有派生类对象天然是一个合法的基类对象,完全满足基类定义的公开接口契约,因此语法上允许派生类到基类的隐式转换,支持用基类引用/指针直接指向派生类实例。引用本质是对象的别名,
ref_prt只是给原本的cld对象加了一个"基类视角"的访问限制:编译器做静态类型检查时,只允许通过ref_prt访问Parent类定义的公开成员,但它从始至终都指向栈上那个真实的Child实例,没有任何内存拷贝发生。
2. 基类引用调用重写函数走派生类实现的底层逻辑
这是C++动态多态的标准行为,和你已经理解的指针场景虚函数调用逻辑完全一致——引用本身就是语法层面受限的安全指针,底层机制没有区别,核心是虚函数表(vtable)和虚指针(vptr)的实现规则:
- 只要类中定义了
virtual修饰的虚函数,编译器就会为这个类生成一张独立的虚函数表,表中按顺序存储该类所有虚函数的实际实现地址:Parent类的虚表中,doB对应的入口地址就是Parent::doB()Child类重写了doB,它的虚表中对应位置存的就是Child::doB()的入口地址
- 每个类实例的内存布局起始位置,都会被编译器自动插入一个虚指针
vptr,这个指针指向实例实际所属类型对应的虚函数表。虚指针是跟着真实对象走的,不会因为指向它的引用/指针是什么类型而改变。 - 当通过基类引用/指针调用虚函数时,编译器不会在编译阶段硬编码要调用的函数地址,而是生成运行时查找逻辑:先读取实际对象内存中的
vptr,找到对应类型的虚函数表,再取出目标虚函数的地址跳转执行,这个过程就是动态绑定。 - 非虚函数不触发动态绑定,编译阶段直接根据引用/指针的静态类型(写代码时声明的类型)确定调用地址,和实际指向的对象类型无关,这也是函数隐藏的典型触发场景。
代码输出的对应逻辑验证
测试代码核心片段逻辑如下:
Child cld; Parent prt = cld; // 值拷贝,发生对象切片,生成独立Parent实例 Parent &ref_prt = cld; // 引用绑定,直接指向原Child实例,无拷贝 prt.doA(); // 非虚函数,静态绑定,prt是Parent实例,调用Parent::doA ref_prt.doA();// 非虚函数,静态绑定,引用静态类型是Parent,调用Parent::doA prt.doB(); // 虚函数,prt是Parent实例,vptr指向Parent虚表,调用Parent::doB ref_prt.doB();// 虚函数,动态绑定,实际对象是Child,vptr指向Child虚表,调用Child::doB
对应运行输出和规则完全匹配:
doA in Parent doA in Parent doB in Parent doB in Child
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

