关闭

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

123人阅读 评论(0) 收藏 举报
分类:

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

    问题一、数据的冗余

    问题二、访问的二意性

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

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

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

   一、 菱形继承:

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

wKiom1cNmZKgmmKmAAAuXCSwYqg368.png

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

wKiom1cNmpPy48ivAAApVk8ruhY766.png

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

wKiom1cNnnTTyUf1AAA61Wbog04930.png

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


    二、虚拟菱形继承

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

wKiom1cNmZKgmmKmAAAuXCSwYqg368.png

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

wKioL1cWBHyAcFWLAAAyVRbvLI0886.png

虚拟继承B1,B2后的D;

wKioL1cWF3DAhrnEAAAjWHER6tE886.png

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

#include<iostream>
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的内存分布情况

wKioL1cWF-nzUh7eAAAxLk4yito290.png


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

wKiom1cWF1jBI1U2AAAl-Opm3OU513.png


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

wKiom1cWF4bCPmJAAAA3MZ3BfCo802.png


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

wKiom1cWGESRz5noAABvGibnQiM459.png


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

wKioL1cWH-nhDUYTAAAfnmTQYro878.png


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


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


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


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


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

本文出自 “滴水” 博客,请务必保留此出处http://10740329.blog.51cto.com/10730329/1763290

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:14104次
    • 积分:196
    • 等级:
    • 排名:千里之外
    • 原创:109篇
    • 转载:7篇
    • 译文:0篇
    • 评论:1条
    文章分类
    最新评论