【C++】深度剖析 · 继承 (虚基表+虚函数表)


菱形继承 深度原理剖析

1. 普通继承内存布局

复制代码
D对象内存排布
[ A成员 ]  // B带来的A
[ B自有成员 ]
[ A成员 ]  // C带来的A
[ C自有成员 ]
[ D自有成员 ]

2. 虚继承内存布局

虚继承不会在派生类中直接拷贝父类成员而是在派生类中增加一个****虚基类指针(vbptr)

  • vbptr:virtual base pointer 虚基类指针

  • 作用:偏移寻址,找到唯一共享的顶层基类对象

    D对象

    1. B类部分:内含 vbptr_B + B独有成员
    2. C类部分:内含 vbptr_C + C独有成员
    3. D自身独有成员
    4. 【唯一一份共享A基类子对象】

3. 虚基类表 vb-table 工作机制

1.每个拥有虚继承的类,都会生成一张虚基类表

2.表中存放:当前子类 到 共享虚基类的内存偏移量

3.访问 A 中成员流程:

1.通过对象拿到 vbptr

2.查虚基类表获取偏移值

3.自身地址 + 偏移 → 定位到唯一共享 A 对象

无论从 B 路径还是 C 路径访问,最终指向同一个 A 实体彻底解决二义性

4. 构造与析构顺序

1. 普通菱形继承

构造顺序:A → B → A → C → D

析构顺序:D → C → A → B → A

A 被构造销毁两次

2. 虚继承菱形继承(C++ 标准规则)

虚基类优先构造!

  1. 最先直接构造唯一共享虚基类 A(仅一次)
  2. 再依次构造普通父类 B、C
  3. 最后构造子类 D

完整顺序:A → B → C → D

析构严格逆序:D → C → B → A

顶层虚基类只构造、析构一次

核心规则:虚基类由最底层派生类直接负责初始化必须在 D 的构造函数初始化列表中显式

调用 A 构造,否则报错

5. 虚继承优缺点

优点

  1. 消除冗余数据,节省内存
  2. 彻底解决菱形继承访问二义性
  3. 统一虚基类状态,所有派生类数据同步

缺点

  1. 引入vbptr 虚基类指针,每个对象增加内存开销
  2. 成员访问需要查表偏移寻址,运行效率略低于普通继承
  3. 构造层级变复杂,初始化写法严格
  4. C++ 语法复杂度大幅提升

单继承、多继承、菱形虚继承 虚函数表

1. 什么是虚函数表 vftable

  1. 含有虚函数的类,编译器自动生成一张虚函数表(数组)
  2. 表中存放:虚函数的地址
  3. 对象头部多出一个 虚函数指针 vfptr****指向本类虚函数表首地址
  4. 调用虚函数流程:对象.vfptr -> 查表 -> 拿到函数地址 -> 调用
  5. 静态函数、普通成员函数、成员变量 不进虚表

2. 重写 (override) 对虚表的影响

子类重写父类虚函数:直接用子类函数地址,覆盖虚表中原父类函数地址

位置不变,只换内容,保证多态调用正确。

一、单继承 虚函数表布局

cpp 复制代码
class Base{
public:
    virtual void fun1(){ }
    virtual void fun2(){ }
};

class Derive : public Base{
public:
    virtual void fun1(){ } // 重写
    virtual void fun3(){ } // 新增虚函数
};
-----------------------------
Base对象内存:
[ vfptr ]  // 虚函数指针

Base虚函数表:
0: &Base::fun1
1: &Base::fun2
-----------------------------
Derive对象:
[ vfptr ]   // 指向子类自己的虚表
[ 继承Base成员 ]
[ 子类独有成员 ]

Derive虚函数表:
0: &Derive::fun1   // 覆盖重写
1: &Base::fun2     // 未重写,保留父类
2: &Derive::fun3   // 子类新增,往后排

二、多继承 虚函数表布局

cpp 复制代码
class A{ virtual void fa(); };
class B{ virtual void fb(); };
class C : public A, public B
{
    virtual void fa();  // 重写A
    virtual void fb();  // 重写B
    virtual void fc();  // 自己新增
};

C对象内存:
[ vfptr_A ]   // 第一张虚表指针(A路线)
[ A成员 ]
[ vfptr_B ]   // 第二张虚表指针(B路线)
[ B成员 ]
[ C自身成员 ]

1. 多继承向上转型原理

复制代码
C c;
A *pa = &c;  // pa指向第一张vfptr位置
B *pb = &c;  // pb自动偏移内存,指向第二张vfptr位置
  • 转哪个父类,就用哪一张虚表
  • 编译器自动完成内存地址偏移
继承类型 虚表数量 新增虚函数存放 核心特点
单继承 1 张 虚表尾部追加 顺序顺延,最简单
多继承 多张 (同父类数) 全部存入第一张表 多 vfptr,转型自动偏移
菱形虚继承 各自独立虚表 + 虚基类表 遵循多继承规则 多表分离,共享顶层基类

2. 地址获取和判断方法


面试必背硬核问答

虚函数表什么时候构建?

编译阶段生成虚表,运行阶段靠 vfptr 寻址调用。

析构函数写成虚函数作用?

父类指针删子类对象,走虚表调用子类析构,完整释放资源。

静态函数能进虚表吗?

不能,无 this 指针,不支持多态。

虚继承和普通继承虚表区别?

虚继承不合并顶层父类虚表,靠偏移指针共享对象,普通继承直接拷贝完整父类虚表与成员。

为什么多继承会出现虚表混乱?

因为存在多个独立虚函数入口,向上转型必须地址偏移才能匹配对应父类虚表。


相关推荐
阳区欠10 分钟前
【LangChain】LLM基础介绍
开发语言·python·langchain
Jinkxs21 分钟前
Java 跨域14-Java 与区块链(Hyperledger)集成
java·开发语言·区块链
晨曦中的暮雨1 小时前
Golang速通(Javaer版)
java·开发语言·后端·golang
小小编程路2 小时前
Python 还有容器类型互转、进制转换、字符编码转换
开发语言·windows·python
Qt程序员2 小时前
Linux RCU 原理与应用
linux·c++·内核·linux内核·rcu
qeen872 小时前
【C++】类与对象之类的默认成员函数(二)
android·c语言·开发语言·c++·笔记·学习
CRMEB系统商城2 小时前
CRMEB多商户系统(Java)v2.3公测版发布
java·开发语言·人工智能·小程序·开源·php
动能小子ohhh2 小时前
DocForge平台的设计与开发--文件上传接口的实现
开发语言·人工智能·python·langchain·ocr·fastapi
满天星83035772 小时前
【Qt】信号和槽(二) (自定义信号和槽)
开发语言·数据库·qt
王老师青少年编程3 小时前
信奥赛C++提高组csp-s之搜索进阶(记忆化搜索案例实践3)
c++·记忆化搜索·方格取数·csp·信奥赛·csp-s·提高组