菱形继承
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了菱形继承相关的知识,希望对你有一定的参考价值。
学习了C++的继承,觉得菱形继承是比较难懂的一部分,通过了解菱形继承,我们可以了解编译器是如何工作的,下面介绍一下菱形继承在内存中的实现方式。
首先简单了解一下虚继承,下面父类和子类的大小分别是多少?
class Base { public: virtual void fun1() { cout << "Base::fun1()" << endl; } public: int _a; char a; }; class Derive:virtual public Base { public: virtual void fun1() { cout << "Derive::fun1()" << endl; } virtual void fun2() { cout << "Derive::fun2()" << endl; } public: int _b; };
上述由于内存对齐,Base类的大小为12,Derive类的大小为24。
为什么?是怎么实现的呢?
由于类Base中存在内存对齐,还包含了虚函数,则含有指向虚函数表的指针,故Base类的大小为4+4+4=12。类Derive里包含:继承的虚函数类,该类的int _b,还有一个指向虚基类的指针。考虑内存对齐,总大小为12+4+4=20,问题是多余的4个字节呢?下面通过介绍菱形继承进行分析。
菱形继承
#include<iostream> using namespace std; typedef void (*FUNC )();//定义函数类型指针 class Base //超类 { public: virtual void fun1() { cout << "Base::fun1()" << endl; } public: int _a; }; class Base1 :public Base //父类 { public: virtual void fun1() { cout << "Base1::fun1()" << endl; } virtual void fun2() { cout << "Base1::fun2()" << endl; } public: int _b; }; class Base2 :public Base //父类 { public: virtual void fun1() { cout << "Base2::fun1()" << endl; } virtual void fun3() { cout << "Base2::fun3()" << endl; } public: int _c; }; class Derive :public Base1 ,public Base2 //子类 { public: virtual void fun1() { cout << "Derive::fun1()" << endl; } virtual void fun2() { cout << "Derive::fun2()" << endl; } virtual void fun3() { cout << "Derive::fun3()" << endl; } virtual void fun4() { cout << "Derive::fun4()" << endl; } public: int _d; }; void PrintTable(int * vTable )//打印出虚函数表 {//虚函数表中结束标志是NULL for ( int i = 0; vTable[i] != 0; i++) { printf( "第%d个虚函数->%p\n" , i, vTable [i]); FUNC f = ( FUNC) vTable[i]; f(); } cout<<endl; } void Test() { Base a; Base1 b; Base2 c; Derive d; cout << "Base->" << sizeof (a) << endl; cout << "Base1->" << sizeof (b) << endl; cout << "Base2->" << sizeof (c) << endl; cout << "Derive->" << sizeof (d) << endl; //d._a = 1;此写法存在二义性,无法访问 d. Base1::_a = 1; //不能从根本上解决二义性 d._b = 2; d._c = 3; d._d = 4; int *vTable = (int*)&d; int *vTable1 = (int*)*(int*)&d; int *vTable2 = (int*)(*((int*)&d + sizeof(Base1) / 4)); cout << "虚函数表地址:" << vTable << endl; //PrintTable(vTable);//访问位置时冲突会发生崩溃 cout << "虚函数表——第一个函数地址:" << vTable1 << endl; PrintTable(vTable1); cout << "虚函数表——第二个函数地址:" << vTable2 << endl; PrintTable(vTable2); }
运行结果如下:
从监视可以看出:Base1、Base2中都有func1();Base1的_vfptr与Base2的_vfptr地址不同,指向的内容也不同。由于Base1的虚表与Base2的虚表都含有Base的fun1(),这种继承存在二义性与冗余性。菱形虚继承解决了这个问题,在定义 Base1,Base2时,需要在Base1和Base2类中的public Base前加 virtual。
菱形虚继承运行结果:
Base1的_vfptr与Base2的_vfptr地址相同,菱形虚拟继承比菱形继承多了一个虚表,专门存放Base,可见将超类存放一份,通过指针使用该类,这样子类中父类与超类公共部分都是同一块存储空间,就解决了二义性与数据冗余问题
0x0031F6C8 + 0xFFFFFFFC(-4) = 0x0031F6C4, 0x0031F6C8 + 0x00000018 = 0x0031F6EC;
0x0031F6D4 + 0xFFFFFFFC(-4) = 0x0031F6D0, 0x0031F6D4 + 0x0000000c = 0x0031F6EC.
结论如下:
在虚继承时,类中会自动加一个指针(_vfptr),该变量指向一个全类共享的偏移量表。
如果该类有虚函数,那么第一项记录着当前子对象相对与虚基类表指针的偏移,是FF FF FF FC(也就是-4),如果没有则是零;第二项是被继承的基类(Bse类)子对象相对于_vfptr指针的偏移量。
本文出自 “Materfer” 博客,请务必保留此出处http://10741357.blog.51cto.com/10731357/1750392
以上是关于菱形继承的主要内容,如果未能解决你的问题,请参考以下文章
C++:继承(继承的概念和性质,赋值兼容规则,菱形继承和虚拟继承)