上一篇我们讨论了在单一继承的情况下,具有虚函数的类对象的内存布局情况。本篇主要讨论在多继承情况下,对象的内存布局。
看例子:
07 | virtual void x(){cout << "left x" << endl;} |
08 | virtual void print1(){cout << "left print" << endl;} |
16 | virtual void y(){cout << "right y" << endl;} |
17 | virtual void print2(){cout << "right print" << endl;} |
22 | class Bottom : public Left, public Right |
25 | virtual void x(){cout << "bottom x" << endl;} |
26 | virtual void y(){cout << "bottom y" << endl;} |
27 | virtual void print3(){cout << "bottom print" << endl;} |
35 | cout << sizeof (Left) << "\t" << sizeof (Right) << "\t" << sizeof (Bottom) <<endl; |
36 | Bottom *b = new Bottom(); |
37 | cout << b << " " << &b->a << " " << &b->b << " " << &b->c << endl; |
41 | typedef void (*Func)( void ); |
43 | pFunc = (Func)*(( int *)*( int *)(b)); |
45 | pFunc = (Func)*(( int *)*( int *)(b)+1); |
47 | pFunc = (Func)*(( int *)*( int *)(b)+2); |
49 | pFunc = (Func)*(( int *)*( int *)(b)+3); |
56 | pFunc = (Func)*(( int *)*(( int *)(b)+2)); |
58 | pFunc = (Func)*(( int *)*(( int *)(b)+2)+1); |
我们分三部分进行讲解:
第一部分:多继承情况下,对象本身(除去虚函数表)的内存布局。
根据代码中first part的输出情况,Left和Right的大小均为8,这个很好理解。int为4字节,加上虚指针的4字节即为8。Bottom的大小难道不应该是16字节吗?三个int总共12字节,另外还有一个虚指针4字节。错!这里Bottom对象中具有两个虚指针,因为它有两个父类,而两个父类均有虚函数。
根据Bottom对象中成员变量的地址输出情况,我们可以知道第一个虚指针位于对象的起始地址处,另外一个虚指针位于从Right继承而来的成员变量b之前。由此,我们可以得到对象的内存布局,如下图所示:
![](http://static.oschina.net/uploads/space/2012/0714/212547_1qry_576409.jpg)
上面的图显示的是继承两个父类的内存布局情况,继承多个父类以此类推。
第二部分:多继承情况下,主要虚函数表的内存布局。
根据代码中second part的输出情况,我们可以看出主要虚函数表中包含Bottom对象的大部分虚函数,它们是:经过覆盖子类Left中的虚函数,Left没有被覆盖的虚函数,经过覆盖的Right中的虚函数,bottom独有的虚函数。但是请注意:bottom不包含Right独有的虚函数,它在次要虚函数表中。内部布局情况如下图所示:
![](http://static.oschina.net/uploads/space/2012/0715/091753_6x8u_576409.jpeg)
主虚函数表中包含所有父类被覆盖的虚函数、第一个父类没有被覆盖的虚函数、以及子类独有的虚函数。需要注意的是:主虚函数表不包含除第一个父类外,其他父类没有被覆盖的虚函数。
第三种情况:多重继承情况下,次要虚函数表的内存布局。
根据代码中third part部分的输出情况,我们可以看出次要虚函数表中包括被子类覆盖的Right父类中的虚函数,以及没有被子类覆盖的Right父类中的虚函数。需要注意的是代码中的红色部分,也许想象中是应该输出Bottom类有而Right类没有的print3虚函数,但是事实并非如此。该虚函数已经包含在主要虚函数表中了,就没有必要包含在次要虚函数表中。之所以要把虚函数表分成多部分,是为了让子类被不同的多个父类指针指向或引用时,能够正确的调整this指针位置,进行动态调用。因此,次要虚函数表就只需要包含相应的父类中被覆盖的虚函数以及未被覆盖的虚函数。内存布局,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/0d58ed3b91bd4a7cefd79d866dda3992.jpeg)
总结下,在具有虚函数类的多继承时,子类包含一个主要虚函数表和一个或多个次要虚函数表。 主虚函数表中包含所有父类被覆盖的虚函数、第一个父类没有被覆盖的虚函数、以及子类独有的虚函数。 次要虚函数表仅包含对应的父类中被子类覆盖的虚函数以及没有被覆盖的虚函数,而不包含其他父类的虚函数以及子类特有的虚函数。
参考文献:
1. 《深入探索C++对象模型》