问题:

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


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


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个字节(两个指针),是因为在子类继承的父类部分会各增加一个指针,作用是指向一个地址,地址中保存着父类增加的指针的地址与超类的地址偏移值,通过地址与偏移值相加,找到超类成员部分,并且两个父类指针都指向的是同一块空间。

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