重复继承

下面我们再来看看,发生重复继承的情况。所谓重复继承,也就是某个基类被间接地重复继承了多次。

下图是一个继承图,我们重载了父类的f()函数。

其类继承的源代码如下所示。其中,每个类都有两个变量,一个是整形(4字节),一个是字符(1字节),而且还有自己的虚函数,自己overwrite父类的虚函数。如子类D中,f()覆盖了超类的函数, f1() 和f2() 覆盖了其父类的虚函数,Df()为自己的虚函数。

class B
{public:int ib;char cb;public:B() :ib(0), cb('B') {}virtual void f() { cout << "B::f()" << endl; }virtual void Bf() { cout << "B::Bf()" << endl; }
};class B1 : public B
{public:int ib1;char cb1;public:B1() :ib1(11), cb1('1') {}virtual void f() { cout << "B1::f()" << endl; }virtual void f1() { cout << "B1::f1()" << endl; }virtual void Bf1() { cout << "B1::Bf1()" << endl; }
};class B2 : public B
{public:int ib2;char cb2;public:B2() :ib2(12), cb2('2') {}virtual void f() { cout << "B2::f()" << endl; }virtual void f2() { cout << "B2::f2()" << endl; }virtual void Bf2() { cout << "B2::Bf2()" << endl; }
};class D : public B1, public B2
{public:int id;char cd;public:D() :id(100), cd('D') {}virtual void f() { cout << "D::f()" << endl; }virtual void f1() { cout << "D::f1()" << endl; }virtual void f2() { cout << "D::f2()" << endl; }virtual void Df() { cout << "D::Df()" << endl; }
};

我们用来存取子类内存布局的代码如下所示:(在VC++ 2003和G++ 3.4.4下)

typedef void(*Fun)(void);
int** pVtab = NULL;
Fun pFun = NULL;D d;
pVtab = (int**)&d;
cout << "[0] D::B1::_vptr->" << endl;pFun = (Fun)pVtab[0][0];
cout << "     [0] ";    pFun();pFun = (Fun)pVtab[0][1];
cout << "     [1] ";    pFun();pFun = (Fun)pVtab[0][2];
cout << "     [2] ";    pFun();pFun = (Fun)pVtab[0][3];
cout << "     [3] ";    pFun();pFun = (Fun)pVtab[0][4];
cout << "     [4] ";    pFun();pFun = (Fun)pVtab[0][5];
cout << "     [5] 0x" << pFun << endl;cout << "[1] B::ib = " << (int)pVtab[1] << endl;
cout << "[2] B::cb = " << (char)pVtab[2] << endl;
cout << "[3] B1::ib1 = " << (int)pVtab[3] << endl;
cout << "[4] B1::cb1 = " << (char)pVtab[4] << endl;cout << "[5] D::B2::_vptr->" << endl;
pFun = (Fun)pVtab[5][0];
cout << "     [0] ";    pFun();pFun = (Fun)pVtab[5][1];
cout << "     [1] ";    pFun();pFun = (Fun)pVtab[5][2];
cout << "     [2] ";    pFun();pFun = (Fun)pVtab[5][3];
cout << "     [3] ";    pFun();pFun = (Fun)pVtab[5][4];
cout << "     [4] 0x" << pFun << endl;cout << "[6] B::ib = " << (int)pVtab[6] << endl;
cout << "[7] B::cb = " << (char)pVtab[7] << endl;
cout << "[8] B2::ib2 = " << (int)pVtab[8] << endl;
cout << "[9] B2::cb2 = " << (char)pVtab[9] << endl;cout << "[10] D::id = " << (int)pVtab[10] << endl;
cout << "[11] D::cd = " << (char)pVtab[11] << endl;

程序运行结果如下:


下面是对于子类实例中的虚函数表的图:

我们可以看见,最顶端的父类B其成员变量存在于B1和B2中,并被D给继承下去了。而在D中,其有B1和B2的实例,于是B的成员在D的实例中存在两份,一份是B1继承而来的,另一份是B2继承而来的。所以,如果我们使用以下语句,则会产生二义性编译错误:

D d;
d.ib = 0;               //二义性错误
d.B1::ib = 1;           //正确
d.B2::ib = 2;           //正确

注意,上面例程中的最后两条语句存取的是两个变量。虽然我们消除了二义性的编译错误,但B类在D中还是有两个实例,这种继承造成了数据的重复,我们叫这种继承为重复继承。重复的基类数据成员可能并不是我们想要的。所以,C++引入了虚基类的概念。

钻石型多重虚拟继承

虚拟继承的出现就是为了解决重复继承中多个间接父类的问题的。钻石型的结构是其最经典的结构。也是我们在这里要讨论的结构:

上述的“重复继承”只需要把B1和B2继承B的语法中加上virtual 关键,就成了虚拟继承,其继承图如下所示:

上图和前面的“重复继承”中的类的内部数据和接口都是完全一样的,只是我们采用了虚拟继承:其省略后的源码如下所示:

class B {……};
class B1 : virtual public B{……};
class B2: virtual public B{……};
class D : public B1, public B2{ …… };

在查看D之前,我们先看一看单一虚拟继承的情况。下面是一段在VC++2003下的测试程序:(因为VC++和GCC的内存而局上有一些细节上的不同,所以这里只给出VC++的程序,GCC下的程序大家可以根据我给出的程序自己仿照着写一个去试一试):

int** pVtab = NULL;
Fun pFun = NULL;
B1 bb1;pVtab = (int**)&bb1;
cout << "[0] B1::_vptr->" << endl;
pFun = (Fun)pVtab[0][0];
cout << "     [0] ";pFun(); //B1::f1();
cout << "     [1] ";
pFun = (Fun)pVtab[0][1];pFun(); //B1::bf1();
cout << "     [2] ";
cout << pVtab[0][2] << endl;cout << "[1] = 0x";
cout << (int*)*((int*)(&bb1) + 1) << endl; //B1::ib1cout << "[2] B1::ib1 = ";
cout << (int)*((int*)(&bb1) + 2) << endl; //B1::ib1cout << "[3] B1::cb1 = ";
cout << (char)*((int*)(&bb1) + 3) << endl; //B1::cb1cout << "[4] = 0x";
cout << (int*)*((int*)(&bb1) + 4) << endl; //NULLcout << "[5] B::_vptr->" << endl;
pFun = (Fun)pVtab[5][0];
cout << "     [0] ";pFun(); //B1::f();
pFun = (Fun)pVtab[5][1];
cout << "     [1] ";pFun(); //B::Bf();
cout << "     [2] ";
cout << "0x" << (Fun)pVtab[5][2] << endl;cout << "[6] B::ib = ";
cout << (int)*((int*)(&bb1) + 6) << endl; //B::ib
cout << "[7] B::cb = ";

其运行结果如下(我结出了GCC的和VC++2003的对比):

这里,大家可以自己对比一下。关于细节上,我会在后面一并再说。

下面的测试程序是看子类D的内存布局,同样是VC++ 2003的(因为VC++和GCC的内存布局上有一些细节上的不同,而VC++的相对要清楚很多,所以这里只给出VC++的程序,GCC下的程序大家可以根据我给出的程序自己仿照着写一个去试一试):

D d;pVtab = (int**)&d;cout << "[0] D::B1::_vptr->" << endl;
pFun = (Fun)pVtab[0][0];
cout << "     [0] ";    pFun(); //D::f1();pFun = (Fun)pVtab[0][1];
cout << "     [1] ";    pFun(); //B1::Bf1();pFun = (Fun)pVtab[0][2];
cout << "     [2] ";    pFun(); //D::Df();pFun = (Fun)pVtab[0][3];
cout << "     [3] ";
cout << pFun << endl;//cout << pVtab[4][2] << endl;cout << "[1] = 0x";
cout << (int*)((&dd) + 1) << endl; //????cout << "[2] B1::ib1 = ";
cout << *((int*)(&dd) + 2) << endl; //B1::ib1cout << "[3] B1::cb1 = ";
cout << (char)*((int*)(&dd) + 3) << endl; //B1::cb1//---------------------cout << "[4] D::B2::_vptr->" << endl;
pFun = (Fun)pVtab[4][0];
cout << "     [0] ";    pFun(); //D::f2();pFun = (Fun)pVtab[4][1];
cout << "     [1] ";    pFun(); //B2::Bf2();
pFun = (Fun)pVtab[4][2];cout << "     [2] ";
cout << pFun << endl;cout << "[5] = 0x";
cout << *((int*)(&dd) + 5) << endl; // ???cout << "[6] B2::ib2 = ";
cout << (int)*((int*)(&dd) + 6) << endl; //B2::ib2cout << "[7] B2::cb2 = ";
cout << (char)*((int*)(&dd) + 7) << endl; //B2::cb2cout << "[8] D::id = ";
cout << *((int*)(&dd) + 8) << endl; //D::idcout << "[9] D::cd = ";
cout << (char)*((int*)(&dd) + 9) << endl;//D::cdcout << "[10]  = 0x";
cout << (int*)*((int*)(&dd) + 10) << endl;//---------------------cout << "[11] D::B::_vptr->" << endl;
pFun = (Fun)pVtab[11][0];
cout << "     [0] ";    pFun(); //D::f();pFun = (Fun)pVtab[11][1];
cout << "     [1] ";    pFun(); //B::Bf();
pFun = (Fun)pVtab[11][2];cout << "     [2] ";
cout << pFun << endl;cout << "[12] B::ib = ";
cout << *((int*)(&dd) + 12) << endl; //B::ibcout << "[13] B::cb = ";
cout << (char)*((int*)(&dd) + 13) << endl;//B::cb

下面给出运行后的结果(分VC++和GCC两部份)

关于虚拟继承的运行结果我就不画图了(前面的作图已经让我产生了很严重的厌倦感,所以就偷个懒了,大家见谅了)

在上面的输出结果中,我用不同的颜色做了一些标明。我们可以看到如下的几点:

1)无论是GCC还是VC++,除了一些细节上的不同,其大体上的对象布局是一样的。也就是说,先是B1(黄色),然后是B2(绿色),接着是D(灰色),而B这个超类(青蓝色)的实例都放在最后的位置。

2)关于虚函数表,尤其是第一个虚表,GCC和VC++有很重大的不一样。但仔细看下来,还是VC++的虚表比较清晰和有逻辑性。

3)VC++和GCC都把B这个超类放到了最后,而VC++有一个NULL分隔符把B和B1和B2的布局分开。GCC则没有。

4)VC++中的内存布局有两个地址我有些不是很明白,在其中我用红色标出了。取其内容是-4。接道理来说,这个指针应该是指向B类实例的内存地址(这个做法就是为了保证重复的父类只有一个实例的技术)。但取值后却不是。这点我目前还并不太清楚,还向大家请教。

5)GCC的内存布局中在B1和B2中则没有指向B的指针。这点可以理解,编译器可以通过计算B1和B2的size而得出B的偏移量。

结束语

C++这门语言是一门比较复杂的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要我们去了解他后面的内存对象。这样我们才能真正的了解C++,从而能够更好的使用C++这门最难的编程语言。

在文章束之前还是介绍一下自己吧。我从事软件研发有十个年头了,目前是软件开发技术主管,技术方面,主攻Unix/C/C++,比较喜欢网络上的技术,比如分布式计算,网格计算,P2P,Ajax等一切和互联网相关的东西。管理方面比较擅长于团队建设,技术趋势分析,项目管理。欢迎大家和我交流,我的MSN和Email是:haoel@hotmail.com
————————————————
版权声明:本文为CSDN博主「haoel」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/haoel/article/details/3081385

C++ 对象的内存布局(下)相关推荐

  1. C++虚继承(三) --- C++ 对象的内存布局(下)(陈皓)

    C++ 对象的内存布局(下) 陈皓 http://blog.csdn.net/haoel <<<点击这里查看上篇 重复继承 下面我们再来看看,发生重复继承的情况.所谓重复继承,也就是 ...

  2. C++ 对象的内存布局

    C++ 对象的内存布局(上) 陈皓 http://blog.csdn.net/haoel 前言 07年12月,我写了一篇<C++虚函数表解析>的文章,引起了大家的兴趣.有很多朋友对我的文章 ...

  3. C++ 对象的内存布局(上)

    原文地址:http://blog.csdn.net/haoel/article/details/3081328 为尊重原作者的创作成果,所以原文全部内容都会保留,但是会适当添加我的理解. 前言 07年 ...

  4. C++对象模型5——类对象的内存布局

    一.类对象的内存布局 1.1.单一继承的类对象布局 示例1 class base { public:int m_fai;int m_faj; }; class derive : public base ...

  5. 动态加载laydate 失效_Java对象的内存布局+反射的原理+动态代理+ 并发和锁+文末彩蛋...

    # 一行代码是怎么运行的 首先,java代码会被编译成字节码,字节码就是java虚拟机定义的一种编码格式,需要java虚拟机才能够解析,java虚拟机需要将字节码转换成机器码才能在cpu上执行. 我们 ...

  6. c++对象的内存布局2--进阶篇---C++ 对象的内存布局(上)

    目录(?)[-] 前言 对象的影响因素 知识复习 单一的一般继承 多重继承 前言 07年12月,我写了一篇<C++虚函数表解析>的文章,引起了大家的兴趣.有很多朋友对我的文章留了言,有鼓励 ...

  7. C++虚继承(二) --- C++ 对象的内存布局(上)(陈皓)

    C++ 对象的内存布局(上) 陈皓 http://blog.csdn.net/haoel 点击这里查看下篇>>> 前言 07年12月,我写了一篇<C++虚函数表解析>的文 ...

  8. JVM从入门到精通(四):内存屏障与JVM指令,对象的内存布局

    JMM 硬件层数据一致性 协议很多,intel 用MESI https://www.cnblogs.com/z00377750/p/9180644.html 现代CPU的数据一致性实现 = 缓存锁(M ...

  9. 对象实例化指针_JVM第三课:一文讲透对象的内存布局和访问方式

    对象的内存布局和访问定位 对象的实例化 对象的创建方式 new Class的newInstance方法 Class> aClass = Class.forName("com.mu.Co ...

  10. 深入理解C++对象模型-对象的内存布局,vptr,vtable

    vtpr的位置:        为了支持多态,C++引入了vtpr和vtable这两个概念.对于每个有虚函数的类,C++都会为其生成一个vtable,并在类中添加一个隐含的数据成员vptr. 对于vp ...

最新文章

  1. 浅谈DToF技术原理
  2. 要如何努力,才干成为非常厉害的人?!
  3. Spatial Transformer Networks(STN)代码分析
  4. STM32 电机教程 27 - ST MCLIB实战之 PID参数调节
  5. 推荐 19 个 github 超牛逼项目!
  6. Python repr() 函数用法及实例(各种类型转字符串)
  7. Ajax技术的一些总结
  8. ora错误匹配java_上周上线碰见的ORA-00054错误回放
  9. 60-10-040-命令-数据的路径间迁移kafka-reassign-partitions.sh
  10. ssh之雇员管理系统(4)-改进的hibernate测试
  11. jquery动画效果总结
  12. steam常用计算机,絮絮叨叨的繁星 篇二:新电脑必备——常用验机和跑分软件汇总...
  13. python培训学堂怎么样_如何评价开智学堂开设的课程?
  14. java 复制excel_利用Java实现复制Excel工作表功能
  15. UEFI Protocol使用
  16. 3dmax顶点动画导入unity_用3dMax给lol人物模型制作表情动画并导入Unity
  17. Android隐藏状态栏实现沉浸式体验
  18. Windows10系统EFI分区和OEM分区的删除方法
  19. php微信支付返回值,php微信支付全记录
  20. 一个游戏中玩家总经验值达到200就可以到达2级,达到400可以升到3级,达到600可升4级,达到800可升5级依此类推(玩家新创建的角色为0经验1级)。游戏中有一种超级经验丹,玩家1级的时候使用超级经

热门文章

  1. Maven项目:Spring Boot版本的办公自动化系统
  2. Spire.Doc 教程:在C#,VB.NET创建、编辑和保存Word
  3. 微软Azure、谷歌GAE、亚马逊AWS比較
  4. iOS的QuickTime Plugin
  5. python file operate example - 2
  6. Storm实验 -- 单词计数2
  7. VOIP通信中影响语音质量的因素
  8. ArcGIS API for Silverlight 使用GeometryService进行河流网格划分(三)
  9. 兼容标准的CSV文件读写类
  10. 安全专家:70GB财务数据被僵尸网络盗窃