struct Employee { ... };

struct Manager : Employee { ... };

struct Worker : Employee { ... };

struct MiddleManager : Manager, Worker { ... };
如果经理类和工人类都继承自雇员类,很自然地,它们每个类都会从雇员类获得一份数据拷贝。如果不作特殊处理,一线经理类的实例将含有两个雇员类实例,它们分别来自两个雇员基类。如果雇员类成员变量不多,问题不严重;如果成员变量众多,则那份多余的拷贝将造成实例生成时的严重开销。更糟的是,这两份不同的雇员实例可能分别被修改,造成数据的不一致。因此,我们需要让经理类和工人类进行特殊的声明,说明它们愿意共享一份雇员基类实例数据。

很不幸,在C++中,这种“共享继承”被称为“虚继承”,把问题搞得似乎很抽象。虚继承的语法很简单,在指定基类时加上virtual关键字即可。

struct Employee { ... };

struct Manager : virtual Employee { ... };

struct Worker : virtual Employee { ... };

struct MiddleManager : Manager, Worker { ... };
使用虚继承,比起单继承和多重继承有更大的实现开销、调用开销。回忆一下,在单继承和多重继承的情况下,内嵌的基类实例地址比起派生类实例地址来,要么地址相同(单继承,以及多重继承的最靠左基类),要么地址相差一个固定偏移量(多重继承的非最靠左基类)。然而,当虚继承时,一般说来,派生类地址和其虚基类地址之间的偏移量是不固定的,因为如果这个派生类又被进一步继承的话,最终派生类会把共享的虚基类实例数据放到一个与上一层派生类不同的偏移量处。请看下例:

struct G : virtual C {

   int g1;

   void gf();

};
译者注:GdGvbptrG(In G, the displacement of G’s virtual base pointer to G)意思是:在G中,G对象的指针与G的虚基类表指针之间的偏移量,在此可见为0,因为G对象内存布局第一项就是虚基类表指针; GdGvbptrC(In G, the displacement of G’s
virtual base pointer to C)意思是:在G中,C对象的指针与G的虚基类表指针之间的偏移量,在此可见为8。

struct H : virtual C {

   int h1;

   void hf();

};


struct I : G, H {

   int i1;

   void _if();

};
暂时不追究vbptr成员变量从何而来。从上面这些图可以直观地看到,在G对象中,内嵌的C基类对象的数据紧跟在G的数据之后,在H对象中,内嵌的C基类对象的数据也紧跟在H的数据之后。但是,在I对象中,内存布局就并非如此了。VC++实现的内存布局中,G对象实例中G对象和C对象之间的偏移,不同于I对象实例中G对象和C对象之间的偏移。当使用指针访问虚基类成员变量时,由于指针可以是指向派生类实例的基类指针,所以,编译器不能根据声明的指针类型计算偏移,而必须找到另一种间接的方法,从派生类指针计算虚基类的位置。

在VC++中,对每个继承自虚基类的类实例,将增加一个隐藏的“位平台上,GdGvptrC是8个字节。同样,在I实例中的G对象实例也有“虚基类表指针”,不过该指针指向一个适用于“G处于I之中”的虚基类表,表中一项为IdGvbptrC,值为20。

观察前面的G、H和I,我们可以得到如下关于VC++虚继承下内存布局的结论:
· 首先排列非虚继承的基类实例;
· 有虚基类时,为每个基类增加一个隐藏的vbptr,除非已经从非虚继承的类那里继承了一个vbptr;
· 排列派生类的新数据成员;
· 在实例最后,排列每个虚基类的一个实例。

该布局安排使得虚基类的位置随着派生类的不同而“浮动不定”,但是,非虚基类因此也就凑在一起,彼此的偏移量固定不变。

C++虚继承初识的更多相关文章

  1. 虚基类&虚继承

    发现这个月准备竞赛完全没有更新哎... 改了下某华大一c++测试题...网上对虚继承讲的要么太繁琐要么不到位,自力更生 #include<iostream> #include<fst ...

  2. C++继承,多重继承,虚继承的构造函数以及析构函数的调用顺序问题

    #include <iostream> using namespace std; class A{ int data_a; public: A(){ data_a = ; cout < ...

  3. C++中虚继承派生类构造函数的正确写法

    最近工作中某个软件功能出现了退化,追查下来发现是一个类的成员变量没有被正确的初始化.这个问题与C++存在虚继承的情况下派生类构造函数的写法有关.在此说明一下错误发生的原因,希望对更多的人有帮助. 我们 ...

  4. C++虚函数、虚继承、对象内存模型(转)

    参考:http://blog.csdn.net/hxz_qlh/article/details/14633361 需要注意的是虚继承.多重继承时类的大小.

  5. 虚函数列表: 取出方法 // 虚函数工作原理和(虚)继承类的内存占用大小计算 32位机器上 sizeof(void *) // 4byte

    #include <iostream> using namespace std; class A { public: A(){} virtual void geta(){ cout < ...

  6. C++对象模型:单继承,多继承,虚继承

    什么是对象模型 有两个概念可以解释C++对象模型: 语言中直接支持面向对象程序设计的部分.对于各种支持的底层实现机制. 类中成员分类 数据成员分为静态和非静态,成员函数有静态非静态以及虚函数 clas ...

  7. C++中的虚继承 & 重载隐藏覆盖的讨论

    虚继承这个东西用的真不多.估计也就是面试的时候会用到吧.. 可以看这篇文章:<关于C++中的虚拟继承的一些总结> 虚拟基类是为解决多重继承而出现的. 如:类D继承自类B1.B2,而类B1. ...

  8. C++ 多继承和虚继承的内存布局(转)

    转自:http://www.oschina.net/translate/cpp-virtual-inheritance 警告. 本文有点技术难度,需要读者了解C++和一些汇编语言知识. 在本文中,我们 ...

  9. C++中的多重继承与虚继承的问题

    1.C++支持多重继承,但是一般情况下,建议使用单一继承. 类D继承自B类和C类,而B类和C类都继承自类A,因此出现下图所示情况: A          A \          / B     C ...

随机推荐

  1. java中URLEncode和URLDecode

    URLEncode和URLDecode用于完成普通字符串和 application/x-www-from-urlencoded MIME字符串之间的相互转化 如果传递的字符串中包含非西欧字符的字符串, ...

  2. GIAC2019 演讲精选 | 面向未来的黑科技——UI2CODE闲鱼基于图片生成跨端代码

    一直以来, 如何从‘视觉稿’精确的还原出 对应的UI侧代码 一直是端侧开发同学工作里消耗比较大的部分,一方面这部分的工作 比较确定缺少技术深度,另一方面视觉设计师也需要投入大量的走查时间,有大量无谓的 ...

  3. python-selenium自动化测试(火狐、谷歌、360浏览器启动)

    一.打开谷歌浏览器 import selenium from selenium import webdriver browser = webdriver.Chrome(executable_path ...

  4. 项目中容易出现的BUG预警

    之前没有记录BUG的习惯导致在同一个坑里边栽了好几次,于是将最近几个项目中遇到的问题整理一下,在进行新项目时预警一遍: 使用携带有搜索功能的分页查询时,注意当用户更改了查询条件但没有点击查询按钮直接点 ...

  5. Java容易搞错的知识点

    一.关于Switch 代码: Java代码 1         public class TestSwitch { 2             public static void main(Stri ...

  6. 本地运行angularjs应用,提示出现跨域问题

    应用介绍: 使用angularjs创建一个简单的公司员工管理页面 使用Chrome打开后,提示存在跨域问题. 和别人交流后,得知使用像Angular,React,Vue框架,在Chrome地址栏中输入 ...

  7. hdu 3339 In Action(迪杰斯特拉+01背包)

    In Action Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total S ...

  8. C#的类

    一.String类 1.Length 字符的长度 string x = Console.ReadLine();int i = x.Length;// Length 是获取字符串的长度(从1开始数)Co ...

  9. centos linux mysql 10060远程错误代码

    Navicat for MySQL远程连接数据错误代码10060 1.登陆远程linux服务器命令界面 vim /etc/sysconfig/iptables  进入防火墙配置修改 增加以下两条防火墙 ...

  10. uni-app 常用框架内置方法 更新中 .....

    获取 登录信息,getStorage 初始化页面数据   请求  下拉刷新页面 加载更多  点击跳转  个人中心  uni.request(OBJECT)   success=成功    fail=失 ...