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

原创 2016年05月30日 15:04:12

    在继承中经常会遇到这种情况:有一个超类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

版权声明:本文为博主原创文章,未经博主允许不得转载。

C++ — 关于菱形虚拟继承对象模型的探究

菱形继承对象模型 我们已经探究菱形继承在继承中的简单的对象模型,当时我们的探究的模型图是这样的: 当时我们还没有说虚函数,所以也没有虚函数表这个概念,但是我们知道了虚函数表以后...
  • Dawn_sf
  • Dawn_sf
  • 2017年04月08日 14:46
  • 354

【C++】菱形虚拟继承(内存布局)

菱形虚拟继承的模式 class A; class B : virtual public A; class C : virtual public A; class D : public B , ...
  • ZDF0414
  • ZDF0414
  • 2015年10月13日 18:50
  • 448

C++里的继承和多态(下)——单继承、多继承、菱形继承(含虚拟函数的继承)

1、带有虚函数的类 class Base { public:                  virtual void FunTest1()                 {      ...

C++ &继承(单继承.多继承.菱形(虚拟)继承)

1:分析菱形继承的问题。 2:剖析虚继承是怎么解决二义性和数据冗余的。 多继承:(一个子类有两个或者两个以上的直接父类) 菱形继承: #include usin...

菱形继承问题分析及其在C++的解决方法(虚继承)

菱形继承问题分析及其在C++的解决方法(虚继承)定义面向对象语言都有一种特性–继承 但存在一种场景(多继承)会掉入陷阱,作简单介绍...

研究菱形虚拟继承和虚表(探索多态的原理)

1.继承 定义的理解         C++中存在继承机制。通过继承,我们可以利用已存在的 数据类型来定义新的数据类型,在新的数据类型中不仅有自己新定义的成员外,还包含以前以前旧的成员,即允许程序员在...

菱形虚拟继承和多态的原理

菱形虚拟继承                    菱形继承(也叫钻石继承)                          结构如下                                ...
  • Te_amo_
  • Te_amo_
  • 2017年02月15日 21:45
  • 268

菱形虚拟继承&虚函数表&对象模型

菱形继承:650) this.width=650;" src="http://s1.51cto.com/wyfs02/M02/7E/9B/wKiom1cFDbHDMc8eAAAx4qLLT30181....

菱形虚拟继承详解

菱形继承是继承里面比较复杂的有一种,在这里我分别对菱形继承、菱形虚拟继承、菱形虚拟继承含虚函数这几种情况 要清楚虚表和虚基表是两个完全不同的概念 虚表:虚函数表,存的是虚函数->多态 虚基表:存的是偏...

多态+菱形虚拟继承(下)

对象的类型:在这里先来看一下在继承体系中搞的人晕头转向的几个术语:下面让我们一起进入多态的世界 多态(polymorphism)一词最初来源于希腊语polumorphos,含义是具有多种形式或形态...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:【C++】菱形继承与虚拟菱形继承的对比分析
举报原因:
原因补充:

(最多只允许输入30个字)