问题:
由于将下图定义为多继承类型时,子类会发生二义性与数据冗余,而用菱形继承时会解决这些问题,菱形继承发生了些什么?又是怎么实现的?
本次试着说明菱形继承的机理(实现方法)
按照上图建立多继承,编写代码:
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可得)
可看出:Base1、Base2中都有func1();Base1的_vfptr与Base2的_vfptr地址不同,指向的内容也不同Base1的虚表与Base2的虚表都含有Base的func(),这种继承存在二义性与冗余性。
在定义 Base1,Base2时,在public Base前加 virtual,将此继承变为菱形继承。
菱形继承运行结果:
菱形继承解决了二义性与冗余性问题。
多继承计算大小得:
菱形继承计算大小得:
将多继承与菱形继承结合起来分析:
由上图可以看出,在菱形继承与多继承时,超类大小一样,但从父类开始大小发生区别,父类多了8个字节,子类多了18个字节。这其中做了些什么?
由于要想消除二义性与冗余性,就得将Base1、Base2中的Base部分变为一份,那只能将Base1、Base2中Base部分变为指针指向Base部分。那具体又是怎么实现的?
打开“d”的内部
发现多了一个Base,再将Base1、Base2、Base都打开。
看到:Base1的_vfptr,Base2的_vfptr,Base的_vfptr地址相同,指向的内容也一样是Base的虚表。
通过上图及调用内存窗口,对相应地址进行分析得到下图
由上图分析可以得到:相比多继承,菱形继承中在子类中会多8个字节(两个指针),是因为在子类继承的父类部分会各增加一个指针,作用是指向一个地址,地址中保存着父类增加的指针的地址与超类的地址偏移值,通过地址与偏移值相加,找到超类成员部分,并且两个父类指针都指向的是同一块空间。
这样子类中父类与超类公共部分都是同一块存储空间,就解决了二义性与数据冗余问题。