您的位置:首页 > 其它

菱形继承及内部实现

2016-03-12 22:53 393 查看
学习了C++的继承后,觉得菱形继承是比较难懂的一部分,通过了解菱形继承,我们可以了解编译器是如何工作的,下面介绍一下菱形继承在内存中的实现方式。
首先简单了解一下虚继承,下面父类和子类的大小分别是多少?
class Base
{
public:
virtual void fun1()
{
cout << "Base::fun1()" << endl;
}
public:
int _a;
char a;
};
class Derive:virtual public Base
{
public:
virtual void fun1()
{
cout << "Derive::fun1()" << endl;
}
virtual void fun2()
{
cout << "Derive::fun2()" << endl;
}
public:
int _b;
};
上述由于内存对齐,Base类的大小为12,Derive类的大小为24。
为什么?是怎么实现的呢?
由于类Base中存在内存对齐,还包含了虚函数,则含有指向虚函数表的指针,故Base类的大小为4+4+4=12。类Derive里包含:继承的虚函数类,该类的int _b,还有一个指向虚基类的指针。考虑内存对齐,总大小为12+4+4=20,问题是多余的4个字节呢?下面通过介绍菱形继承进行分析。
菱形继承



#include<iostream>
using namespace std;

typedef void (*FUNC )();//定义函数类型指针

class Base //超类
{
public:
virtual void fun1()
{
cout << "Base::fun1()" << endl;
}
public:
int _a;
};

class Base1 :public Base //父类
{
public:
virtual void fun1()
{
cout << "Base1::fun1()" << endl;
}
virtual void fun2()
{
cout << "Base1::fun2()" << endl;
}
public:
int _b;
};

class Base2 :public Base //父类
{
public:
virtual void fun1()
{
cout << "Base2::fun1()" << endl;
}
virtual void fun3()
{
cout << "Base2::fun3()" << endl;
}
public:
int _c;
};

class Derive :public Base1 ,public Base2 //子类
{
public:
virtual void fun1()
{
cout << "Derive::fun1()" << endl;
}
virtual void fun2()
{
cout << "Derive::fun2()" << endl;
}
virtual void fun3()
{
cout << "Derive::fun3()" << endl;
}
virtual void fun4()
{
cout << "Derive::fun4()" << endl;
}
public:
int _d;
};

void PrintTable(int * vTable )//打印出虚函数表
{//虚函数表中结束标志是NULL
for ( int i = 0; vTable[i] != 0; i++)
{
printf( "第%d个虚函数->%p\n" , i, vTable [i]);
FUNC f = ( FUNC) vTable[i];
f();
}
cout<<endl;
}

void Test()
{
Base a;
Base1 b;
Base2 c;
Derive d;
cout << "Base->" << sizeof (a) << endl;
cout << "Base1->" << sizeof (b) << endl;
cout << "Base2->" << sizeof (c) << endl;
cout << "Derive->" << sizeof (d) << endl;

//d._a = 1;此写法存在二义性,无法访问
d. Base1::_a = 1; //不能从根本上解决二义性
d._b = 2;
d._c = 3;
d._d = 4;

int *vTable = (int*)&d;
int *vTable1 = (int*)*(int*)&d;
int *vTable2 = (int*)(*((int*)&d + sizeof(Base1) / 4));
cout << "虚函数表地址:" << vTable << endl;
cout << "虚函数表——第一个函数地址:" << vTable1 << endl;
PrintTable(vTable1);
cout << "虚函数表——第二个函数地址:" << vTable2 << endl;
PrintTable(vTable2);
}
菱形继承运行结果如下:







从监视可以看出:Base1、Base2中都有func1();Base1的_vfptr与Base2的_vfptr地址不同,指向的内容也不同。由于Base1的虚表与Base2的虚表都含有Base的fun1(),这种继承存在二义性与冗余性。菱形虚继承解决了这个问题,在定义 Base1,Base2时,需要在Base1和Base2类中的public Base前加 virtual。菱形虚继承运行结果:



Base1的_vfptr与Base2的_vfptr地址相同,菱形虚拟继承比菱形继承多了一个虚表,专门存放Base,可见将超类存放一份,通过指针使用该类,这样子类中父类与超类公共部分都是同一块存储空间,就解决了二义性与数据冗余问题


指针_vfptr:0x0031F6C8和0x0031F6D40x0031F6C8 + 0xFFFFFFFC(-4) = 0x0031F6C4, 0x0031F6C8 + 0x00000018 = 0x0031F6EC; 0x0031F6D4 + 0xFFFFFFFC(-4) = 0x0031F6D0, 0x0031F6D4 + 0x0000000c = 0x0031F6EC.结论如下: 在虚继承时,类中会自动加一个指针(_vfptr),该变量指向一个全类共享的偏移量表。
如上图所示偏移量的说明:如果该类有虚函数,那么第一项记录着当前子对象相对与虚基类表指针的偏移,是FF FF FF FC(也就是-4),如果没有则是零;第二项是被继承的基类(Base类)子对象(d._a)相对于_vfptr指针的偏移量。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  虚继承 菱形继承