【C++】菱形继承与虚拟菱形继承的对比分析-创新互联

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

合山ssl适用于网站、小程序/APP、API接口等需要进行数据传输应用场景,ssl证书未来市场广阔!成为创新互联的ssl证书销售渠道,可以享受市场价格4-6折优惠!如果有意向欢迎电话联系或者加微信:13518219792(备注:SSL证书合作)期待与您的合作!

   问题一、数据的冗余

   问题二、访问的二意性

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

   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字节

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

创新互联www.cdcxhl.cn,专业提供香港、美国云服务器,动态BGP最优骨干路由自动选择,持续稳定高效的网络助力业务部署。公司持有工信部办法的idc、isp许可证, 机房独有T级流量清洗系统配攻击溯源,准确进行流量调度,确保服务器高可用性。佳节活动现已开启,新人活动云服务器买多久送多久。


新闻标题:【C++】菱形继承与虚拟菱形继承的对比分析-创新互联
本文来源:http://csdahua.cn/article/ccpdsi.html
扫二维码与项目经理沟通

我们在微信上24小时期待你的声音

解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流