继承是C++作为OOD程序设计语言的三大特征(封装,继承,多态)之一,单一非多态继承是比较好理解的,本文主要讲解GCC环境下的多重继承和虚拟继承的对象内存布局。
一、多重继承
      先看几个类的定义:
 
| 07 |  classLeft : publicTop | 
 
| 13 |  classRight : publicTop | 
 
| 19 |  classBottom : publicLeft, publicRight | 
 
 
 
 
          不难想象,Left和Right类的内存布局如下图所示:
 
         ![]() 
        
          我们如下进行验证:
 
| 1 | Left *left = newLeft(); | 
 
| 3 | cout << left <<  '\t'<< top << endl; | 
 
| 4 | Right *right = newRight(); | 
 
| 6 | cout << right << '\t'<< top << endl; | 
 
 
 
 
         从输出结果可以看出,父类指针top指向子类对象left和right的起始地址,与上述内存布局吻合。
 
         在非虚拟多重继承的情况下,子类的内存布局是什么样子的呢?如下所示:
           ![]()
          可以看出,Bottom类由于继承了Left和Right,而Left和Right又分别继承了Top。因此,Bottom包含了Top两次!
          下面进行验证:
 
| 1 | Bottom *bottom = newBottom();    | 
 
 
 
| 2 | cout << bottom << '\t'<< top << '\t'<< left << endl; | 
 
| 5 | cout << bottom << '\t'<< top << '\t'<< right << endl; | 
 
 
 
          从输出结果可以看出,left指针和right指针分别指向了bottom对象中它们所处的位置: 
      
  由于bottom对象中存在两部分top对象,因此不能直接用top指针指向bottom对象,因为编译器不知道你的意图到底是指向left中的
bottom部分,还是right中的bottom部分。需要进行转换才可以。如果需要通过bottom指针分别访问left和right中的top部
分,可以如下:  bottom->Left::a,  bottom->Right::a。
 
         好了,到这里讲完了非虚拟继承下的多重继承的内存布局情况,相信大家应该有一个比较清晰的认识了。最重要的一点是: 多重继承时,父类共同继承的祖父类会在子类中有多份存在。
 
 
二、虚拟继承
     平时讨论的最多的是虚函数,很少涉及到虚拟继承的情况。那么,虚拟继承到底是一个什么概念呢?
      先来看一个例子:    
 
| 10 |  classChild
 : virtualpublicFather | 
 
| 18 |     cout
 << sizeof(Father)
 << '\t'<< sizeof(Child)
 << endl; | 
 
| 20 |     cout
 << &child << '\t'<<
 &child.b << '\t'<<
 &child.a << endl; | 
 
 
 
 
 
 
      对,你没有看错,类的大小输出不是4   8,而是4   12。虚拟继承时,编译器会在子类中安插上一个虚表指针。
      从输出的对象成员地址来看,我们可以得到Child类的如下内存布局:
         ![]()
      现在我们对多重继承的例子进行改造:
 
| 07 | classLeft
 : virtualpublicTop | 
 
| 13 | classRight
 : virtualpublicTop | 
 
| 19 | classBottom
 : publicLeft, publicRight | 
 
 
 
 
     把Left和Right改成了虚拟继承Top。
 
      从上面验证简单虚拟继承时,编译器安插虚表指针的例子,我们可以想象出此时Bottom类的对象内存布局如下:
        ![]()
        对,你没有看错!虚拟继承时,子类只有父类共同继承的祖父类的一份存在。这其实也就是虚拟继承的最大用途。此时,Top,Left,Right和Bottom对象的大小分别为:4  ,12  ,12 ,24。
         既然有虚表指针了,那么Bottom的虚表是什么样的呢?请看:
        ![]()
        有了虚表,内存布局情况一目了然。下面我们进行验证:
 
| 1 | Bottom
 *bottom = newBottom(); | 
 
| 3 | cout
 << bottom << '\t'<<
 top << endl; | 
 
| 5 | cout
 << bottom << '\t'<<
 left << endl; | 
 
| 7 | cout
 << bottom << '\t'<<
 right << endl; | 
 
 
 
  根据输出结果,我们可以知道指针的指向情况:
    ![]()
由于引入了虚指针和虚表,left指针和right指针可以根据虚表提供的偏移量信息,轻松访问到Top::a。
到此为止,已经讨论清楚了多重继承和虚拟继承下的对象内存布局情况。总结下:非虚拟多重继承时,子类会有父类
共同继承祖父类的多份存在;虚拟继承时,子类会被安插一个虚拟指针;多重虚拟继承时,子类只有父类共同继承祖父类的一
份存在。通过父类的虚拟指针,可以正确地访问祖父类中的成员。