浅析GCC下C++多重继承 & 虚拟继承的对象内存布局 - 问笑

继承是C++作为OOD程序设计语言的三大特征(封装,继承,多态)之一,单一非多态继承是比较好理解的,本文主要讲解GCC环境下的多重继承和虚拟继承的对象内存布局。

一、多重继承

      先看几个类的定义:

01class   Top
02  {
03public :
04       int   a;
05  };
06 
07  class   Left :  public   Top
08  {
09public :
10       int   b;
11  };
12 
13  class   Right :  public   Top
14  {
15public :
16       int   c;
17  };
18 
19  class   Bottom :  public   Left,  public   Right
20  {
21public :
22       int   d;
23  };

          不难想象,Left和Right类的内存布局如下图所示:

                   

          我们如下进行验证:

1Left *left =  new   Left();
2Top *top = left;
3cout << left <<   '\t'   << top << endl;//输出:0x902c008       0x902c008
4Right *right =  new   Right();
5top = right;
6cout << right <<  '\t'   << top << endl;//输出:0x902c018       0x902c018

         从输出结果可以看出,父类指针top指向子类对象left和right的起始地址,与上述内存布局吻合。

         在非虚拟多重继承的情况下,子类的内存布局是什么样子的呢?如下所示:

            

          可以看出,Bottom类由于继承了Left和Right,而Left和Right又分别继承了Top。因此,Bottom包含了Top两次!

          下面进行验证:

1Bottom *bottom =  new   Bottom();   
1//  top = bottom;     //error: ‘Top’ is an ambiguous base of ‘Bottom’   
1top = (Left *)bottom;
1left = bottom;
2cout << bottom <<  '\t'   << top <<  '\t'   << left << endl;//输出:0x9930028 0x9930028 0x9930028
3top = (Right *)bottom;
4right = bottom;
5cout << bottom <<  '\t'   << top <<  '\t'   << right << endl;//输出:0x9930028 0x9930030 0x9930030
         从输出结果可以看出,left指针和right指针分别指向了bottom对象中它们所处的位置:  
                  
        由于bottom对象中存在两部分top对象,因此不能直接用top指针指向bottom对象,因为编译器不知道你的意图到底是指向left中的 bottom部分,还是right中的bottom部分。需要进行转换才可以。如果需要通过bottom指针分别访问left和right中的top部 分,可以如下:  bottom->Left::a,  bottom->Right::a。

         好了,到这里讲完了非虚拟继承下的多重继承的内存布局情况,相信大家应该有一个比较清晰的认识了。最重要的一点是:  多重继承时,父类共同继承的祖父类会在子类中有多份存在。

二、虚拟继承

     平时讨论的最多的是虚函数,很少涉及到虚拟继承的情况。那么,虚拟继承到底是一个什么概念呢?

      先来看一个例子:    

01#include <iostream>
02using   namespace   std;
03 
04  class   Father
05  {
06public :
07     int   a;
08  };
09 
10  class   Child :  virtual   public   Father
11  {
12  public :
13     int   b;
14  };
15 
16  int   main()
17  {
18     cout <<  sizeof (Father) <<  '\t'   <<  sizeof(Child) << endl; //输出:4   12
19     Child child;
20     cout << &child <<  '\t'   << &child.b <<  '\t'  << &child.a << endl; //输出:0xbfc08124 0xbfc08128 0xbfc0812c
21     return   0;
22  }

      对,你没有看错,类的大小输出不是4   8,而是4   12。 虚拟继承时,编译器会在子类中安插上一个虚表指针。

       从输出的对象成员地址来看,我们可以得到Child类的如下内存布局:

          

       现在我们对多重继承的例子进行改造:

01class   Top
02{
03public :
04      int   a;
05};
06 
07class   Left :  virtual   public   Top
08{
09public :
10      int   b;
11};
12 
13class   Right :  virtual   public   Top
14{
15public :
16     int   c;
17};
18 
19class   Bottom :  public   Left,  public   Right
20{
21public :
22     int   d;
23};

     把Left和Right改成了虚拟继承Top。

      从上面验证简单虚拟继承时,编译器安插虚表指针的例子,我们可以想象出此时Bottom类的对象内存布局如下:

         

        对,你没有看错! 虚拟继承时,子类只有父类共同继承的祖父类的一份存在 。这其实也就是虚拟继承的最大用途。此时,Top,Left,Right和Bottom对象的大小分别为:4  ,12  ,12 ,24。

          既然有虚表指针了,那么Bottom的虚表是什么样的呢?请看:

        

        有了虚表,内存布局情况一目了然。下面我们进行验证:

1Bottom *bottom =  new   Bottom();
2top = bottom;
3cout << bottom <<  '\t'   << top << endl;//输出:0x9fa5028       0x9fa503c
4Left *left = bottom;
5cout << bottom <<  '\t'   << left << endl;//输出:0x9fa5028       0x9fa5028
6Right *right = bottom;
7cout << bottom <<  '\t'   << right << endl;//输出:0x9fa5028       0x9fa5030

  根据输出结果,我们可以知道指针的指向情况:

      

由于引入了虚指针和虚表,left指针和right指针可以根据虚表提供的偏移量信息,轻松访问到Top::a。

到此为止,已经讨论清楚了多重继承和虚拟继承下的对象内存布局情况。总结下: 非虚拟多重继承时,子类会有父类

共同继承祖父类的多份存在;虚拟继承时,子类会被安插一个虚拟指针;多重虚拟继承时,子类只有父类共同继承祖父类的一

份存在。通过父类的虚拟指针,可以正确地访问祖父类中的成员

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值