学习了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个字节呢?下面通过介绍菱形继承进行分析。

菱形继承

wKioL1bkKozjcg8TAAAj4Jr1yuc607.png

#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);
}

菱形继承运行结果如下

wKioL1bkLbSALMP9AABNqb9Zo_0784.png

wKiom1bkLy2Th72MAAA6TLrpQBg789.png

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

菱形虚继承运行结果:

wKiom1bkRmyxtqI5AAA6z0EvWZU818.png

wKiom1bkRiCytB8wAAA36mZSa2w824.png

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

wKiom1bkSRewZj00AABdkU9DPXc640.png

指针_vfptr:0x0031F6C8和0x0031F6D4

0x0031F6C8 + 0xFFFFFFFC(-4) = 0x0031F6C4, 0x0031F6C8 + 0x00000018 = 0x0031F6EC; 

0x0031F6D4 + 0xFFFFFFFC(-4) = 0x0031F6D0, 0x0031F6D4 + 0x0000000c = 0x0031F6EC.

结论如下:

   在虚继承时,类中会自动加一个指针(_vfptr),该变量指向一个全类共享的偏移量表。

   如上图所示偏移量的说明:如果该类有虚函数,那么第一项记录着当前子对象相对与虚基类表指针的偏移,是FF FF FF FC(也就是-4),如果没有则是零;第二项是被继承的基类(Base类)子对象(d._a)相对于_vfptr指针的偏移量。