当前位置: 首页 > news >正文

虚函数详解(二)—— 虚函数与多继承

目录

前言

一、多继承对虚函数机制的挑战

二、多重 vptr 与对象内存布局

三、虚函数表的结构与 thunk 机制

3.1 主虚函数表与次级虚函数表

3.2 为什么需要 thunk?

3.3 this 调整量的计算

四、通过不同基类指针调用——全过程解析

4.1 通过第一基类指针调用

4.2 通过第二基类指针调用覆盖函数

4.3 通过第二基类指针调用未覆盖函数

4.4 调用新增虚函数

五、构造函数与析构函数中的 vptr 切换

六、覆盖多个基类中的同名虚函数

七、对 dynamic_cast 的影响

八、性能与内存开销考量

九、回看与展望


前言

        在上一篇文章中,我们详细探索了单继承下的虚函数机制:每个类拥有一张虚函数表,每个对象通过一个虚函数指针(vptr)来找到正确的函数实现。单继承呈现出一种优雅的线性结构,派生类的虚表只需在前缀保持一致的前提下,替换或追加新的函数指针。

        然而,当多继承*登场时,事情变得复杂起来。一个派生类可能同时继承自两个或更多基类,这些基类各自独立,可能都包含虚函数。如此一来,对象中不再只有一个 vptr,虚函数表也不再是简单的单表结构,“this 指针调整” 和 “thunk” 技术成为保证多态正确性的关键。本文将深入剖析这些机制,带领你穿过多继承的虚函数迷雾。

一、多继承对虚函数机制的挑战

        考虑一个简单的多继承场景:
 

class Base1 { public:     int b1_data = 1;     virtual void f1() { std::cout << "Base1::f1\n"; }     virtual void common() { std::cout << "Base1::common\n"; } }; class Base2 { public:     int b2_data = 2;     virtual void f2() { std::cout << "Base2::f2\n"; }     virtual void common() { std::cout << "Base2::common\n"; } }; class Derived : public Base1, public Base2 { public:     int d_data = 3;     void f1() override { std::cout << "Derived::f1\n"; }     void f2() override { std::cout << "Derived::f2\n"; }     void common() override { std::cout << "Derived::common\n"; }     virtual void f3() { std::cout << "Derived::f3\n"; } };

        `Derived` 同时继承了 `Base1` 和 `Base2`,它覆盖了两基类的全部虚函数,并新增了自己的虚函数 `f3`。问题随之而来:

        1). 一个 `Derived` 对象内部存储形式是什么?`Base1` 和 `Base2` 子对象需要各自保持独立吗?
        2). 通过 `Base2*` 调用 `f2()`,如何正确找到 `Derived::f2`?
        3). 如果通过 `Base2*` 调用 `Derived::common()`,`this` 指针应该如何传递?毕竟 `common()` 里可能访问 `Derived::d_data`,而通过 `Base2` 指针拿到的 `this` 指向的是 `Base2` 子对象的首地址。

        C++ 编译器通过多重 vptr、多张虚函数表和thunk技术完美解决了这些问题。

二、多重 vptr 与对象内存布局

        在单继承下,对象中只有一个 vptr,放在对象的最前端。多继承则不同:每个具有虚函数的基类,都要求在派生的对象中保留自己的 vptr,以保证基类指针能够独立工作。

        对于上述 `Derived` 类,典型的内存布局如下(假设 64 位系统,8 字节 vptr):
Derived 对象内存布局:

+-------------------+ | vptr1 (主表)      |  <-- 指向 Derived 的主虚函数表 (与 Base1 共用) +-------------------+ | b1_data = 1       |  // Base1 成员 +-------------------+ | vptr2 (次级表)    |  <-- 指向 Derived 的次级虚函数表 (为 Base2 定制) +-------------------+ | b2_data = 2       |  // Base2 成员 +-------------------+ | d_data = 3        |  // Derived 自身成员 +-------------------+

        这里出现了两个 vptr:`vptr1` 位于对象起始处,属于 `Base1` 子对象;`vptr2` 位于 `Base2` 子对象起始处(偏移量为 `sizeof(Base1)`)。每个 vptr 指向的内容并不相同。`vptr1` 指向主虚表,`vptr2` 指向为 `Base2` 定制的次级虚表。正是这些不同的虚表完成了函数地址的正确分发。

        当我们以不同基类指针指涉该对象时:

Derived d; Base1* pb1 = &d;   // pb1 指向对象首地址 Base2* pb2 = &d;   // pb2 指向对象内 Base2 子对象的地址 (自动偏移)

        因为 `Base2` 子对象并非在对象开头,`pb2` 的值实际上比 `&d` 大了一个偏移量

http://www.jsqmd.com/news/734011/

相关文章:

  • 欧姆龙PLC数据采集实战:5分钟教你用Node-RED通过FINS/TCP协议读取CIO区数据
  • 你知道吗?其实这些都是AI——智能垃圾分类
  • Meshtastic-Android 项目教程
  • 开源项目合规指南:从PyWxDump案例看技术开发的边界与责任
  • SubsCheck-Win-GUI安全使用手册:规避风险与合规操作
  • 怪物猎人世界数据可视化革命:HunterPie高效狩猎完全指南
  • 04 删除字符串中的相邻重复项
  • 深入Linux FrameBuffer:从`fb_var_screeninfo`的字段看懂屏幕时序与分辨率设置
  • 别再手动装环境了!用Docker Compose一键部署企业内训系统PlayEdu(附完整配置流程)
  • Midscene.js终极指南:如何用AI视觉模型实现跨平台UI自动化
  • 如何用HTML转Figma工具实现网页设计逆向工程:5个实战技巧与完整指南
  • 你知道吗?其实这些都是AI——艺术品鉴定AI
  • 开发阶段 -- 详设完善
  • PCL2启动器下载问题终极解决指南:3步告别资源损坏烦恼
  • ComfyUI-Impact-Pack图像增强实战:从零构建专业级AI绘画工作流
  • 医疗设备采购必看:串口屏选型避坑指南,质量、价格、交期、服务 - 浴缸里的巡洋舰
  • React Sortable Tree动画效果实现:平滑过渡和视觉反馈终极指南
  • Flow Launcher集成ChatGPT:打造桌面AI助手,提升开发与办公效率
  • 你知道吗?其实这些都是AI——音乐创作AI
  • 不只是画板:用Cadence 17.4的SigXplorer,我这样优化了EEPROM模块的拓扑结构
  • 专业视频对比分析工具:深度解析video-compare的实现原理与实战应用
  • Redis - List
  • XGP存档提取技术解析:架构设计与跨平台迁移实战指南
  • 预推免线下复试全记录:从华工、暨大到湖大,我的‘赶考’日程与面试踩坑复盘
  • 如何免费解锁Cursor Pro功能?cursor-free-vip工具完整使用指南
  • 你知道吗?其实这些都是AI——智能交通管理系统
  • DroidPlugin性能优化:提升插件应用运行效率的10个关键技巧
  • YOLOv13涨点改进| TGRS 2026 |全网独家创新、注意力改进篇| 引入PMM 金字塔掩码Mamba模块,逐步整合深层语义信息与浅层细节信息,含多种改进,助力小目标检测、图像分割高效涨点
  • C++高频面试题总结(一)
  • MCP 2026多租户隔离能力深度评测(2026 Q1权威基准测试报告首发):98.7%租户间资源泄露拦截率如何达成?