您的位置:首页 > 其它

菱形继承的内部实现方式

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

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




按照上图建立多继承,编写代码:
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个字节(两个指针),是因为在子类继承的父类部分会各增加一个指针,作用是指向一个地址,地址中保存着父类增加的指针的地址与超类的地址偏移值,通过地址与偏移值相加,找到超类成员部分,并且两个父类指针都指向的是同一块空间。 这样子类中父类与超类公共部分都是同一块存储空间,就解决了二义性与数据冗余问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息