【C++】菱形继承与虚拟菱形继承的对比分析
在继承中经常会遇到这种情况:有一个超类A,子类B1,B2继承了A,而类D又继承了父类B1,B2。在这种情况下如果按照我们以前的正常的菱形继承的话会有一个问题是子类C会继承两次超类A中的成员,当在C中想访问继承来自B1,B2中A的元素会出现两个问题:
问题一、数据的冗余
问题二、访问的二意性
出现了这种问题那么我们该如何解决呢?
C++中为了解决这个问题引入了虚拟菱形继承,那么虚拟菱形继承是怎么解决的呢?
首先给大家先画两个图比较下菱形继承和虚拟菱形继承在继承时在内存中的成员分布情况:
一、 菱形继承:
没有继承以前的超类A和父类B1,B2;
继承超类A以后的B1,B2;
子类D继承B1,B2以后的内存分布情况
通过图我们可以看出菱形继承存在很多的数据冗余,如超类A的成员ia,ca都有两份,访问时也会出先二义性的错误。
二、虚拟菱形继承
没有继承以前的超类A和父类B1,B2;
虚拟继承超类A以后的B1,B2;
虚拟继承B1,B2后的D;
看完分布图以后,我们看下代码和D在内存中的分布
#include<iostream>usingnamespacestd;classA{public:intia;charca;public:A():ia(0),ca('A'){}virtualvoidf(){cout<<"A::f()"<<endl;}virtualvoidBf(){cout<<"A::Af()"<<endl;}};classB1:virtualpublicA{public:intib1;charcb1;public:B1():ib1(1),cb1('1'){}virtualvoidf(){cout<<"B1::f()"<<endl;}virtualvoidf1(){cout<<"B1::f1()"<<endl;}virtualvoidB1f(){cout<<"B1::B1f()"<<endl;}};classB2:virtualpublicA{public:intib2;charcb2;public:B2():ib2(2),cb2('2'){}virtualvoidf(){cout<<"B2::f()"<<endl;}virtualvoidf2(){cout<<"B2::f2()"<<endl;}virtualvoidB2f(){cout<<"B2::B2f()"<<endl;}};classD:publicB1,publicB2{public:intid;charcd;public:D():id(3),cd('D'){}virtualvoidf(){cout<<"D::f()"<<endl;}virtualvoidf1(){cout<<"D::f1()"<<endl;}virtualvoidf2(){cout<<"D::f2()"<<endl;}virtualvoidDf(){cout<<"D::Df()"<<endl;}};typedefvoid(*Fun)();voidPrintVTable(int*VTable){cout<<"虚表地址>"<<VTable<<endl;for(inti=0;VTable[i]!=0;++i){printf("第%d个虚函数地址:0X%x,->",i,VTable[i]);Funf=(Fun)VTable[i];f();}}voidtest(){Aa;B1b1;B2b2;Dd1;cout<<"sizeof(A)::"<<sizeof(a)<<endl;cout<<"sizeof(B1)::"<<sizeof(b1)<<endl;cout<<"sizeof(B2)::"<<sizeof(b2)<<endl;cout<<"sizeof(D)::"<<sizeof(d1)<<endl;int*VTable=(int*)(*(int*)&d1);PrintVTable(VTable);cout<<"虚基表指针->:"<<(int*)((int*)&d1+1)<<endl;cout<<"B1::ib1="<<*(int*)((int*)&d1+2)<<endl;cout<<"B1::cb1="<<(char)*((int*)&d1+3)<<endl;VTable=(int*)*((int*)&d1+4);PrintVTable(VTable);cout<<"虚基表指针->:"<<(int*)((int*)&d1+5)<<endl;cout<<"B2::ib2="<<*(int*)((int*)&d1+6)<<endl;cout<<"B2::cb2="<<(char)*((int*)&d1+7)<<endl;cout<<"D::ID="<<*((int*)&d1+8)<<endl;cout<<"D::cd="<<(char)*((int*)&d1+9)<<endl;cout<<"虚基表的偏移地址->:"<<(int*)((int*)&d1+10)<<endl;VTable=(int*)*((int*)&d1+11);PrintVTable(VTable);cout<<"A::ia="<<*(int*)((int*)&d1+12)<<endl;cout<<"A::ca="<<(char)*((int*)&d1+13)<<endl;}intmain(){test();system("pause");return0;}
一、父类b1的内存分布情况
二、父类b2的内存分布情况
三、子类d1的内存分布情况
这些都跟前面画图分析的一样。我们再看一下每个类的大小:
将菱形继承与虚拟菱形继承做比较:
按照正常情况下:在菱形继承与虚拟菱形继承时,超类大小一样,但从父类开始大小发生区别,父类多了12个字节,子类多了8个字节。
由于要想消除二义性与冗余性,就得将B1、B2中的A部分变为一份,那只能将B1、B2中A中共同的部分变为指针指向Base部分。为什么会这样呢?
一、对于父类B1、B2来说因为多产生了三个指针,前图中没画出来,可以参照子类D的图,通过虚拟继承,多增加了一个虚基表指针,一个虚基表的偏移地址,另外还继承了A的虚表,作用是指向一个地址,地址中保存着父类增加的指针的地址与超类的地址偏移值,通过地址与偏移值相加,找到超类成员部分,并且两个父类指针都指向的是同一块空间。所以多了12个字节。
二、同理,对于子类D来说在同时多了这些东西的同时减去重复继承的超类的成员最后就只是多了8字节
通过这种处理子类中父类与超类公共部分都是同一块存储空间,就可以解决菱形继承的二义性与数据冗余问题了。
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。