【C++】菱形继承与虚拟菱形继承的对比分析

    在继承中经常会遇到这种情况:有一个超类A,子类B1,B2继承了A,而类D又继承了父类B1,B2。在这种情况下如果按照我们以前的正常的菱形继承的话会有一个问题是子类C会继承两次超类A中的成员,当在C中想访问继承来自B1,B2中A的元素会出现两个问题:

创新互联建站基于成都重庆香港及美国等地区分布式IDC机房数据中心构建的电信大带宽,联通大带宽,移动大带宽,多线BGP大带宽租用,是为众多客户提供专业服务器托管报价,主机托管价格性价比高,为金融证券行业IDC机房托管,ai人工智能服务器托管提供bgp线路100M独享,G口带宽及机柜租用的专业成都idc公司。

    问题一、数据的冗余

    问题二、访问的二意性

出现了这种问题那么我们该如何解决呢?

    C++中为了解决这个问题引入了虚拟菱形继承,那么虚拟菱形继承是怎么解决的呢?

首先给大家先画两个图比较下菱形继承和虚拟菱形继承在继承时在内存中的成员分布情况:

   一、 菱形继承:

没有继承以前的超类A和父类B1,B2;

【C++】菱形继承与虚拟菱形继承的对比分析

继承超类A以后的B1,B2;

【C++】菱形继承与虚拟菱形继承的对比分析

子类D继承B1,B2以后的内存分布情况

【C++】菱形继承与虚拟菱形继承的对比分析

    通过图我们可以看出菱形继承存在很多的数据冗余,如超类A的成员ia,ca都有两份,访问时也会出先二义性的错误。

    二、虚拟菱形继承

没有继承以前的超类A和父类B1,B2;

【C++】菱形继承与虚拟菱形继承的对比分析

虚拟继承超类A以后的B1,B2;

【C++】菱形继承与虚拟菱形继承的对比分析

虚拟继承B1,B2后的D;

【C++】菱形继承与虚拟菱形继承的对比分析

看完分布图以后,我们看下代码和D在内存中的分布

#include
using namespace std;

class A
{
public:
    int ia;
    char ca;
public:
    A()
    :ia(0)
    , ca('A')
    {}

    virtual void f()
    {
        cout << "A::f()" << endl;
    }

    virtual void Bf()
    {
        cout << "A::Af()" << endl;
    }
};

class B1:virtual public A
{
public:
    int ib1;
    char cb1;
public:
    B1()
    :ib1(1)
    , cb1('1')
    {}
    virtual void f()
    {
        cout << "B1::f()" << endl;
    }

    virtual void f1()
    {
        cout << "B1::f1()" << endl;
    }

    virtual void B1f()
    {
        cout << "B1::B1f()" << endl;
    }

};

class B2:virtual public A
{
public:
    int ib2;
    char cb2;
public:
    B2()
    :ib2(2)
    , cb2('2')
    {}

    virtual void f()
    {
        cout << "B2::f()" << endl;
    }

    virtual void f2()
    {
        cout << "B2::f2()" << endl;
    }

    virtual void B2f()
    {
        cout << "B2::B2f()" << endl;
    }

};

class D :public B1,public B2
{
public:
    int id;
    char cd;
public:
    D()
        :id(3)
        , cd('D')
    {}

    virtual void f()
    {
        cout << "D::f()" << endl;
    }

    virtual void f1()
    {
        cout << "D::f1()" << endl;
    }

    virtual void f2()
    {
        cout << "D::f2()" << endl;
    }

    virtual void Df()
    {
        cout << "D::Df()" << endl;
    }

};

typedef void(*Fun)();
void PrintVTable(int* VTable)
{
    cout << " 虚表地址>" << VTable << endl;

    for (int i = 0; VTable[i] != 0; ++i)
    {
        printf(" 第%d个虚函数地址 :0X%x,->", i, VTable[i]);
        Fun f = (Fun)VTable[i];
        f();
    }
}



void test()
{
    A a;
    B1 b1;
    B2 b2;
    D d1;

    cout << "sizeof(A)::" << sizeof(a) << endl;
    cout << "sizeof(B1)::" << sizeof(b1) << endl;
    cout << "sizeof(B2)::" << sizeof(b2) << endl;
    cout << "sizeof(D)::" << sizeof(d1) << endl;

    int* VTable = (int*)(*(int*)&d1);
    PrintVTable(VTable);
    cout << "        虚基表指针->: " << (int*)((int*)&d1 + 1) << endl;
    cout << "         B1::ib1 = " << *(int*)((int*)&d1 + 2) << endl;
    cout << "         B1::cb1 =" << (char)*((int*)&d1 + 3) << endl;

    VTable = (int*)*((int*)&d1 + 4);
    PrintVTable(VTable);
    cout << "        虚基表指针->:" << (int*)((int*)&d1 + 5) << endl;
    cout << "         B2::ib2 =" << *(int*)((int*)&d1 + 6) << endl;
    cout << "         B2::cb2 =" << (char)*((int*)&d1 + 7) << endl;

    cout << "         D::ID =" << *((int*)&d1 + 8) << endl;
    cout << "         D::cd =" << (char)*((int*)&d1 + 9) << endl;
    cout << " 虚基表的偏移地址->:"<<(int*)((int*)&d1 + 10) << endl;
    VTable = (int*)*((int*)&d1 + 11);
    PrintVTable(VTable);
    cout << "         A::ia =" << *(int*)((int*)&d1 + 12) << endl;
    cout << "         A::ca =" << (char)*((int*)&d1 + 13) << endl;
    
}

int main()
{
    test();
    system("pause");
    return 0;
}

一、父类b1的内存分布情况

【C++】菱形继承与虚拟菱形继承的对比分析

二、父类b2的内存分布情况

【C++】菱形继承与虚拟菱形继承的对比分析

三、子类d1的内存分布情况

【C++】菱形继承与虚拟菱形继承的对比分析

这些都跟前面画图分析的一样。我们再看一下每个类的大小:

【C++】菱形继承与虚拟菱形继承的对比分析

将菱形继承与虚拟菱形继承做比较:

【C++】菱形继承与虚拟菱形继承的对比分析

  按照正常情况下:在菱形继承与虚拟菱形继承时,超类大小一样,但从父类开始大小发生区别,父类多了12个字节,子类多了8个字节。

  由于要想消除二义性与冗余性,就得将B1、B2中的A部分变为一份,那只能将B1、B2中A中共同的部分变为指针指向Base部分。为什么会这样呢?

  一、对于父类B1、B2来说因为多产生了三个指针,前图中没画出来,可以参照子类D的图,通过虚拟继承,多增加了一个虚基表指针,一个虚基表的偏移地址,另外还继承了A的虚表,作用是指向一个地址,地址中保存着父类增加的指针的地址与超类的地址偏移值,通过地址与偏移值相加,找到超类成员部分,并且两个父类指针都指向的是同一块空间。所以多了12个字节。

  二、同理,对于子类D来说在同时多了这些东西的同时减去重复继承的超类的成员最后就只是多了8字节

  通过这种处理子类中父类与超类公共部分都是同一块存储空间,就可以解决菱形继承的二义性与数据冗余问题了。


新闻标题:【C++】菱形继承与虚拟菱形继承的对比分析
文章位置:http://lszwz.com/article/ieoshg.html

其他资讯

售后响应及时

7×24小时客服热线

数据备份

更安全、更高效、更稳定

价格公道精准

项目经理精准报价不弄虚作假

合作无风险

重合同讲信誉,无效全额退款