【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 指针,不支持多态。

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

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

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

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


相关推荐
砍材农夫2 小时前
物联网 基于netty构建mqtt协议规范(发布/订阅模式)
java·开发语言·物联网·netty
techdashen2 小时前
Rust 泛型 vs Java 泛型:它们看起来相似,但骨子里截然不同
java·开发语言·rust
一只旭宝2 小时前
【C加加入门精讲15】:IO流缓冲区、字符串流、缓冲流及STL vector容器零基础实战教程一、博客前言
开发语言·c++
alwaysrun2 小时前
C++之高性能跨平台日志库spdlog
c++·后端·编程语言
我不是懒洋洋2 小时前
手写数字识别:从零实现一个卷积神经网络(CNN)
c++
在坚持一下我可没意见2 小时前
Python 修仙修炼录 08:字典秘境,参悟键值玄机
开发语言·笔记·python·入门·字典
BestOrNothing_20152 小时前
C++零基础到工程实战(5.1):初识函数—定义调用、参数返回值、栈区内存与变量作用域分析
c++·生命周期·作用域·变量·函数·栈内存
luck_bor2 小时前
Map&Stream流
java·开发语言
阿文的代码库2 小时前
如何在C++中使用标准库的智能指针
开发语言·c++·算法