虚继承主要解决多重继承会在子类中存在多份拷贝的问题,这不仅浪费空间,而且存在二义性。

在之前的 C++ 继承中已经说过虚继承基本概念,这里不再赘述。这篇文章主要探究虚继承的原理。文章中多处给出了类实例对象的内存布局,查看其内存布局时,使用 VS 工具 /d1 reportAllClassLayout 进行查看,关于这个工具的详细介绍,请点击这里。

虚继承的实现原理

虚继承的底层实现一般与编译器相关,一般会通过虚基类表指针虚基类表实现,先看如下这个程序:

#pragma pack(1)class A
{public:int a;
};class B : virtual public A
{public:int b;
};class C : virtual public A
{public:int c;
};

其内存布局图如下所示:

> cl /d1 reportSingleClassLayoutB CppTest.cpp
class B size(12):+---0      | {vbptr}4      | b+---+--- (virtual base A)8      | a+---B::$vbtable@:0      | 01      | 8 (Bd(B+0)A)
vbi:       class  offset o.vbptr  o.vbte fVtorDispA       8       0       4 0
----------------------------------------------------
> cl /d1 reportSingleClassLayoutC CppTest.cpp
class C size(12):+---0      | {vbptr}4      | c+---+--- (virtual base A)8      | a+---C::$vbtable@:0      | 01      | 8 (Cd(C+0)A)
vbi:       class  offset o.vbptr  o.vbte fVtorDispA       8       0       4 0

从中可以看出,每个虚继承的子类都有一个虚基类表指针 vbptr(virtual base table pointer,占用一个指针的大小,32 位下为4字节,64 位为 8 字节)和该指针指向一个虚基类表(额外的空间,不占用实例对象的空间),上面的程序中 B 虚继承 A,那么 B 类中将会有一个虚基类表指针 vbptr,C 类虚继承 A 类,也是一样。该 vbptr 指针指向一个虚基类表,关于虚基类表下面再详细说。

现在我们再看一个例子:

#pragma pack(1)class A
{public:int a;
};class B : virtual public A
{public:int b;
};class C : virtual public A
{public:int c;
};class D : public B, public C
{public:int d;
};

这次我们使用类 D 多重继承类 B 和类 C(B 和 C 都是虚继承 A),看一下内存布局图:

> cl /d1 reportSingleClassLayoutD CppTest.cpp
class D size(24):+---0      | +--- (base class B)0      | | {vbptr}4      | | b| +---8      | +--- (base class C)8      | | {vbptr}
12      | | c| +---
16      | d+---+--- (virtual base A)
20      | a+---D::$vbtable@B@:0      | 01      | 20 (Dd(B+0)A)D::$vbtable@C@:0      | 01      | 12 (Dd(C+0)A)
vbi:       class  offset o.vbptr  o.vbte fVtorDispA      20       0       4 0

需要强调的是,虚基类依旧会在子类中存在拷贝,但仅仅只存在一份;我们看到虚基类 A 仅仅在类 D 的实例对象中存在一份,也就是上面内存布局里的 virtual base A。当继承的子类被当作父类继承时,虚基类表指针也会被继承;我们上面已经测试过 B 虚继承 A,C 虚继承 A 都会在各自的实例对象中多出一个虚基类表指针 vbptr 指向一个虚基类表,现在 D 类继承与 B 类和 C 类,其类中的虚基类表指针也会被继承。我们也可以从上面的内存布局中看到 D 中类 B 和类 C 中都没有类 A 的成员而是都多了一个虚基类表指针 vbptr,而且类 D 中只有一份虚基类 A 的成员,所以奥秘就在 类 B 和类 C 中的虚基类表指针 vbptr 上。

我们再看类 B 和类 C 的虚基类表的结构,该表中记录了虚基类 A 与本类的偏移地址,因为现在虚基类 A 在类 D 的实例对象中只能存在一份,所以类 B 和类 C 中只要通过虚基类表中给的偏移量访问就能访问到虚基类 A。上面的内存布局中,仅存的一份虚基类 A 的地址相较于类 D 的实例对象的首地址偏移量为 20,而类 B 的 vbptr 相较于类 D 的实例对象的首地址偏移量为 0,所以类 B 中想要访问虚基类 A 需要偏移 20 个字节,所以虚基类表中记录的与虚基类 A 的偏移量为 20,而类 C 的访问虚基类 A 的偏移量为 12,也是同样可以这样计算出来。这样就可以找到虚基类 A 的数据成员,而且类 B 和类 C 对虚基类 A 的数据的修改都是在同一地址,这样就不用像普通菱形继承那样维持着公共基类(也就是这里说的虚基类)的两份同样的拷贝了,同时也能节省一点空间。

我们可以联系到多态中虚函数表,这两者有一定的相似之处,不过虚基类表存储的时虚基类相对当前实例对象的偏移,而虚函数表则存储的是虚函数地址。

到这里,关于虚基类表的探究就已经结束了,我们对虚继承的原理也有了一定的认识,还可以自己在程序中访问虚基类表,其基本思路如下:

D* d = new D;
std::cout << "[0] ->" << *(int*)(*(int*)d) << std::endl;
std::cout << "[1] ->" << *(((int*)(*(int*)d)) + 1)<< std::endl; // 偏移量20
std::cout << "b value  : " << *((int*)d + 1) << std::endl;
std::cout << "[0] ->" << *(int*)(*((int*)d + 2)) << std::endl;
std::cout << "[1] ->" << *((int*)(*((int*)d + 2)) + 1) << std::endl; // 偏移量12
std::cout << "c value  : " << *((int*)d + 3) << std::endl;
std::cout << "d value  : " << *((int*)d + 4) << std::endl;
std::cout << "a value  : " << *((int*)d + 5) << std::endl;

参考:
C++ 虚继承实现原理(虚基类表指针与虚基类表)
【C++拾遗】 从内存布局看C++虚继承的实现原理

C++虚继承中的虚基类表相关推荐

  1. C++多继承中重写不同基类中相同原型的虚函数

    在C++多继承体系当中,在派生类中可以重写不同基类中的虚函数.下面就是一个例子: class CBaseA  {  public:  virtual void TestA();  };  class ...

  2. C++中虚继承产生的虚基类指针和虚基类表,虚函数产生的虚函数指针和虚函数表

    本博客主要通过查看类的内容的变化,深入探讨有关虚指针和虚表的问题. 一.虚继承产生的虚基类表指针和虚基类表 如下代码:写一个棱形继承,父类Base,子类Son1和Son2虚继承Base,又来一个类Gr ...

  3. C++ day22 继承(二)基类指针数组通过虚方法实现智能的多态

    继承一共有三种: 公有继承 私有继承 保护继承 文章目录 公有继承 基类和派生类的关系 is-a(用公有继承表示"是一种"的关系) has-a uses-a is-like-a i ...

  4. 【C++】继承和派生、虚继承和虚基类、虚基类表和虚基类指针

    继承和派生.虚继承和虚基类.虚基类表和虚基类指针 继承和派生 继承概述 继承基本概念 派生类中的成员 继承的内容 派生类定义 派生类访问控制 对象构造和析构 对象构造和析构的调用顺序 继承中的构造和析 ...

  5. C++虚继承中构造函数和析构函数顺序问题以及原理

    多重继承的问题:多个类B,C,-继承同一个类A导致如果X继承了B,C,-那么在X中将还有多个A中成员的拷贝,如果想要访问A中的成员如果不加名字空间将会导致二义性,这种拷贝大多是没有实际意义的,为了避免 ...

  6. c++ 虚函数多态、纯虚函数、虚函数表指针、虚基类表指针详解

    文章目录 静态多态.动态多态 虚函数 哪些函数类型不可以被定义成虚函数? 虚函数的访问方式 析构函数中的虚函数 虚函数表指针 vptr 多继承下的虚函数表 虚基类表指针 bptr 纯虚函数 抽象类 虚 ...

  7. Cpp 对象模型探索 / 虚基类表作用

    一.结论 虚基类表的作用是帮助编译器找到该类中的虚基类中各个成员变量在内存布局中的位置. 虚基类表中的值是偏移值,即:各个虚基类的成员变量在子类中的内存布局中相对于虚函数指针的偏移值. 二.栗子 1. ...

  8. 9-4:C++多态之单继承和多继承中的虚函数表

    文章目录 (1)单继承中的虚函数表 (2)多继承中的虚函数表 (1)单继承中的虚函数表 如下继承体系中,fun1函数重写,fun2未被重写,B类中fun3和fun4也被定义为了虚函数 #include ...

  9. C++学习 对象模型之虚基类,虚基类表,虚基类表指针

    1.虚基类 什么是虚基类,虚基类的作用是什么? 首先虚基类是为了解决多继承产生的二义性问题,范例代码如下: #include "stdafx.h" #include <std ...

最新文章

  1. 链表问题5——反转部分单向链表
  2. 题解——HDU 1848 Fibonacci again and again
  3. 修改路由表来使路由分流
  4. 第十七届第一场智能车竞速校内赛,比往年来的早一些
  5. Ganglia 应用实践
  6. C++ 中类的内存布局
  7. Le Chapitre VI
  8. python基本数据类型的结构和使用方法
  9. 设计模式系列漫谈之二 - 工厂方法模式
  10. 《SAS编程与数据挖掘商业案例》学习笔记之三
  11. 光伏农业七大问题不解决 投资者恐“先驱”变“先烈”
  12. 什么?是Transformer位置编码
  13. 有线数字电视机顶盒的基本原理
  14. 大津法(最大类间方差法OTSU)
  15. java.lang.ClassCastException: com.sun.proxy.$Proxy7 cannot be cast to comms.service.message
  16. 前端高级进阶13本经典书籍
  17. SSH Key的生成和使用
  18. 自助装机配置专家点评
  19. 各项异性扩散滤波 -- OpenCV实现
  20. 快来看看你的苹果手机还能卖多少钱?2022最新苹果手机回收报价单

热门文章

  1. 【地理信息技术】 上机06 土壤侵蚀危险性建模分析
  2. test指令两个操作数一样
  3. 腾讯云服务器使用密码账号SSH登录
  4. AMEsim 几个启动错误解决方法
  5. ps滤镜插件怎么安装上去,ps神经网络滤镜安装包
  6. 竟然有如此好用的读Android源码利器
  7. java cohesion_[Java][概念]Higher cohesion(高凝聚性)_Looser coupling(低相依性)
  8. 开拓高主频CPU场景,硅云第二代云服务器S2近日正式发布
  9. 11 python 安装scikits.audiolab记录
  10. Android生成简单的图片验证码