You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 07:18:24