菱形继承的内部实现方式

来源:互联网 发布:java解压tar.gz的命令 编辑:程序博客网 时间:2024/05/22 01:06

问题:

    由于将下图定义为多继承类型时,子类会发生二义性与数据冗余,而用菱形继承时会解决这些问题,菱形继承发生了些什么?又是怎么实现的?


本次试着说明菱形继承的机理(实现方法)


wKiom1bgKTngOQHSAAAjUsL9Uyw429.png

按照上图建立多继承,编写代码:

class Base{public: virtual void func1() {  cout << "Base::func1()" << endl; }protected: int _a;};class Base1: public Base{public: virtual void func1() {  cout << "Base1::func1()" << endl; } virtual void func3() {  cout << "Base1::func3()" << endl; }protected: int _b;};class Base2 : public Base{public: virtual void func2() {  cout << "Base2::func2()" << endl; } virtual void func4() {  cout << "Base2::func4()" << endl; }protected: int _c;};class Derive : public Base1, public Base2{public: virtual void func1()  {  cout << "Derive::func1()" << endl; } virtual void func2() {  cout << "Derive::func2()" << endl; } virtual void func3() {  cout << "Derive::func3()" << endl; } virtual void func4() {  cout << "Derive::func4()" << endl; } virtual void func5() {  cout << "Derive::func5()" << endl; }protected: int _d;};typedef void(*FUNC)();void pfun(int *vTable){ for (int i = 0; vTable[i] != 0; ++i) {  printf("第 %d 个虚函数-> %p\n", i, vTable[i]);  FUNC f = (FUNC)vTable[i];  f(); }}
void test6(){ Base a; Base1 b; Base2 c; Derive d; int sb = sizeof(a); int sb1 = sizeof(b); int sb2 = sizeof(c); int sd = sizeof(d); pfun((int*)*(int*)&d); printf("\n"); pfun((int*)(*(int*)&d + 20)); printf("\n"); }


多继承运行结果:(虚表指针地址可由运行时“d”的_vfptr可得)

wKiom1bgM1riUaGxAADpLgo_HOY917.jpg


wKiom1bgN2PRMdxHAAHnDd-It2w063.jpg

可看出:Base1、Base2中都有func1();Base1的_vfptr与Base2的_vfptr地址不同,指向的内容也不同Base1的虚表与Base2的虚表都含有Base的func(),这种继承存在二义性与冗余性。


在定义 Base1,Base2时,在public Base前加 virtual,将此继承变为菱形继承。

菱形继承运行结果:

wKiom1bgMymB1eMVAADb-C6zoY4066.jpg


菱形继承解决了二义性与冗余性问题。

多继承计算大小得:

wKioL1bgI6ziV-70AACmJqhQLuE847.jpg


菱形继承计算大小得:

wKiom1bgJPrQYBWnAACqoxSNGeE350.jpg


将多继承与菱形继承结合起来分析:

wKiom1bgJUbRaf5rAAAanl3Sv2U975.png


    由上图可以看出,在菱形继承与多继承时,超类大小一样,但从父类开始大小发生区别,父类多了8个字节,子类多了18个字节。这其中做了些什么?
    由于要想消除二义性与冗余性,就得将Base1、Base2中的Base部分变为一份,那只能将Base1、Base2中Base部分变为指针指向Base部分。那具体又是怎么实现的?     


打开“d”的内部

wKioL1bgN0fy52wnAADLCiml51Q829.jpg


发现多了一个Base,再将Base1、Base2、Base都打开。

wKiom1bgOsGCVGioAAIdMHMU-0A256.jpg


看到:Base1的_vfptr,Base2的_vfptr,Base的_vfptr地址相同,指向的内容也一样是Base的虚表。

wKioL1bhBpHBRiweAAQl_-YZy1Q066.jpg

通过上图及调用内存窗口,对相应地址进行分析得到下图

wKiom1bi08rSJTXJAATZT2ySyKM038.jpg

    由上图分析可以得到:相比多继承,菱形继承中在子类中会多8个字节(两个指针),是因为在子类继承的父类部分会各增加一个指针,作用是指向一个地址,地址中保存着父类增加的指针的地址与超类的地址偏移值,通过地址与偏移值相加,找到超类成员部分,并且两个父类指针都指向的是同一块空间。

    这样子类中父类与超类公共部分都是同一块存储空间,就解决了二义性与数据冗余问题。



0 0
原创粉丝点击