菱形继承的内部实现方式

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了菱形继承的内部实现方式相关的知识,希望对你有一定的参考价值。

问题:

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


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


技术分享

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

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

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



以上是关于菱形继承的内部实现方式的主要内容,如果未能解决你的问题,请参考以下文章

Python菱形继承的初始化问题和继承顺序

C++_继承(菱形继承与虚基表)

类的继承派生组合菱形的继承多态

C++继承

C++之继承详解

C++中的继承