菱形继承和虚继承、对象模型和虚基表

1.菱形继承(钻石继承):两个子类继承同一父类,而又有子类同时继承这两个子类。例如B,C两个类同时继承A,但是又有一个D类同时继承B,C类。
图一

2.菱形继承的对象模型

class A 
{
public:
    int  _a;
};

class B :public A 
{
public:
    int _b;
};
class C :public A
{
public:
    int _c;
};
class D :public B,public C
{
public:
    int _d;
};

上述的代码对应的菱形继承的对象模型如下图所示

图二

3.菱形继承所带来的问题
(1)由上图的对象模型可以看出,D的对象中有两份A成员,造成了数据的冗余。使用sizeof去验证,可以发现d的大小为20个字节。
2

(2)会造成基类子对象重复,即二义性。造成访问基类的成员不明确。

3

4.解决菱形继承所带来的问题
(1)要解决二义性很简单,可以显示的指定访问哪个父类的成员,但是这个还是不能从根本上解决这个问题。如:

D d;
d.B::_a;
//d.C::_a;
//上述两种方法取其一就可以规避这个问题

(2)要从本质上解决上述问题,需要采用虚继承:虚继承是一种机制,类通过虚继承指出它希望共享虚基类的状态。对给定的虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象,共享基类子对象称为虚基类。虚基类用virtual声明继承关系就行了。这样一来,D就只有A的一份拷贝。
如:

class A 
{
public:
    int  _a;
};

class B : virtual public A 
{
public:
    int _b;
};
class C :virtual public A
{
public:
    int _c;
};
class D :public B,public C
{
public:
    int _d;
};

采用虚继承和显示指定访问父类成员,对对象所产生的的影响有什么不同呢?显示指定访问并不能起到一改全改的作用,那么就会造成一个对象d有两个不相同的成员_a,明显是不符合常规的。
3

 1. 显然我们不用显示的指定访问哪个父类的成员,但是可以清晰看到d的大小并不是期望中的16,而是24个字节,这是为什么呢?

6
5.深入理解虚继承
现在来了解多出来的四个字节究竟是什么?首先我们通过调试——>内存查看对象d的地址的数据,可以看到如下现象:

4
(1)(2)中存放的数据究竟是用来表示什么的呢?再将d._a分别赋值成为0和4,通过内存窗口观察d的地址的数据
2
由图可以看到末排的地址存放的数据刚好是d._a的值的大小,那么多出来的第一排和第三排的数据看起来似乎是两个地址,输入地址查看一下数据
3

明显可以看到两个地址存放的数据是每个对象相对于基类的成员的偏移量,对应的每个地址所代表的意义如下:
1

这样也就可以解释为什么d的大小为24个字节因为虚继承引入了间接性指针
6.在虚继承的前提下,重新建立对象模型
3

7.在菱形继承且为虚继承的前提下,讨论虚基表为什么首先不存偏移量,而是在存偏移量之前预留了一个0x0000 0000的位置呢?
(1)首先,设置一个菱形继承,且为虚继承,每个子类既重写了父类的虚函数,还拥有自己的虚函数

class A
{
public:
    virtual void f1()
    {
        cout << "A::f1()" << endl;
        cout << endl;
    }
    virtual void f2()
    {
        cout << "A::f2()" << endl;
        cout << endl;
    }
    int _a;
};

class B :virtual public A
{
public:
    virtual void f1()
    {
        cout << "B::f1()" << endl;
        cout << endl;
    }
    virtual void f3()
    {
        cout << "B::f3()" << endl;
        cout << endl;
    }

    int _b;
};
class C :virtual public A
{
public:
    virtual void f1()
    {
        cout << "C::f1()" << endl;
        cout << endl;
    }
    virtual void f4()
    {
        cout << "C::f4()" << endl;
        cout << endl;
    }
    int _c;
};

class D :public B,public C
{
public:
    virtual void f1()
    {
        cout << "D::f1()" << endl;
        cout << endl;
    }

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

    int _d;
};

用菱形继承的模型图来表示关系就是:
3
通过实例化出对象d,并查看内存可以看到对象布局,可以看到有五个类似地址的部分,一一通过内存窗口查看内容,发现三张为虚表(存放虚函数),两张为虚基表(存放偏移量的),在下图中用不同的颜色标注出来了:
3
通过内存窗口查看各个虚基表的地址:
3
可以看到虚基表的开始预留一个位置是用来存放虚基表和虚表的地址之差的。
内存分布模型:
4

那么可以看到实例化出来的对象d的对象模型为:
4

至于虚表存放的虚函数顺序是怎么存放的:
http://blog.csdn.net/skyroben/article/details/68192874

发布了27 篇原创文章 · 获赞 0 · 访问量 663

猜你喜欢

转载自blog.csdn.net/weixin_43839785/article/details/104506767
今日推荐