菱形继承 深度原理剖析
1. 普通继承内存布局
D对象内存排布 [ A成员 ] // B带来的A [ B自有成员 ] [ A成员 ] // C带来的A [ C自有成员 ] [ D自有成员 ]2. 虚继承内存布局
虚继承不会在派生类中直接拷贝父类成员而是在派生类中增加一个****虚基类指针(vbptr)
vbptr:virtual base pointer 虚基类指针
作用:偏移寻址,找到唯一共享的顶层基类对象
D对象
- B类部分:内含 vbptr_B + B独有成员
- C类部分:内含 vbptr_C + C独有成员
- D自身独有成员
- 【唯一一份共享A基类子对象】
3. 虚基类表 vb-table 工作机制
1.每个拥有虚继承的类,都会生成一张虚基类表
2.表中存放:当前子类 到 共享虚基类的内存偏移量
3.访问 A 中成员流程:
1.通过对象拿到
vbptr2.查虚基类表获取偏移值
3.自身地址 + 偏移 → 定位到唯一共享 A 对象
无论从 B 路径还是 C 路径访问,最终指向同一个 A 实体彻底解决二义性
4. 构造与析构顺序
1. 普通菱形继承
构造顺序:
A → B → A → C → D析构顺序:
D → C → A → B → AA 被构造销毁两次
2. 虚继承菱形继承(C++ 标准规则)
虚基类优先构造!
- 最先直接构造唯一共享虚基类 A(仅一次)
- 再依次构造普通父类 B、C
- 最后构造子类 D
完整顺序:A → B → C → D
析构严格逆序:D → C → B → A
顶层虚基类只构造、析构一次
核心规则:虚基类由最底层派生类直接负责初始化必须在 D 的构造函数初始化列表中显式
调用 A 构造,否则报错
5. 虚继承优缺点
优点
- 消除冗余数据,节省内存
- 彻底解决菱形继承访问二义性
- 统一虚基类状态,所有派生类数据同步
缺点
- 引入vbptr 虚基类指针,每个对象增加内存开销
- 成员访问需要查表偏移寻址,运行效率略低于普通继承
- 构造层级变复杂,初始化写法严格
- C++ 语法复杂度大幅提升
单继承、多继承、菱形虚继承 虚函数表
1. 什么是虚函数表 vftable
- 含有虚函数的类,编译器自动生成一张虚函数表(数组)
- 表中存放:虚函数的地址
- 对象头部多出一个 虚函数指针 vfptr****指向本类虚函数表首地址
- 调用虚函数流程:
对象.vfptr -> 查表 -> 拿到函数地址 -> 调用- 静态函数、普通成员函数、成员变量 不进虚表
2. 重写 (override) 对虚表的影响
子类重写父类虚函数:直接用子类函数地址,覆盖虚表中原父类函数地址
位置不变,只换内容,保证多态调用正确。
一、单继承 虚函数表布局
cppclass 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 // 子类新增,往后排二、多继承 虚函数表布局
cppclass 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 指针,不支持多态。
虚继承和普通继承虚表区别?
虚继承不合并顶层父类虚表,靠偏移指针共享对象,普通继承直接拷贝完整父类虚表与成员。
为什么多继承会出现虚表混乱?
因为存在多个独立虚函数入口,向上转型必须地址偏移才能匹配对应父类虚表。





