虚继承解决了菱形继承中最派生类拥有多个间接父类实例的情况。虚继承的派生类的内存布局与普通继承很多不同,主要体现在:

  • 虚继承的子类,如果本身定义了新的虚函数,则编译器为其生成一个虚函数指针(vptr)以及一张虚函数表。该vptr位于对象内存最前面。
    • vs非虚继承:直接扩展父类虚函数表。
  • 虚继承的子类也单独保留了父类的vprt与虚函数表。这部分内容接与子类内容以一个四字节的0来分界。
  • 虚继承的子类对象中,含有四字节的虚表指针偏移值。

为了分析最后的菱形继承,我们还是先从单虚继承继承开始。

1 虚基类表解析

在C++对象模型中,虚继承而来的子类会生成一个隐藏的虚基类指针(vbptr),在Microsoft Visual C++中,虚基类表指针总是在虚函数表指针之后,因而,对某个类实例来说,如果它有虚基类指针,那么虚基类指针可能在实例的0字节偏移处(该类没有vptr时,vbptr就处于类实例内存布局的最前面,否则vptr处于类实例内存布局的最前面),也可能在类实例的4字节偏移处。

一个类的虚基类指针指向的虚基类表,与虚函数表一样,虚基类表也由多个条目组成,条目中存放的是偏移值。第一个条目存放虚基类表指针(vbptr)所在地址到该类内存首地址的偏移值,由第一段的分析我们知道,这个偏移值为0(类没有vptr)或者-4(类有虚函数,此时有vptr)。我们通过一张图来更好地理解。
3 虚继承 - 图1
3 虚继承 - 图2

2 简单虚继承

如果我们的B1类虚继承于B类:

  1. //类的内容与前面相同
  2. class B{...}
  3. class B1 : virtual public B

3 虚继承 - 图3
根据我们前面对虚继承的派生类的内存布局的分析,B1类的对象模型应该是这样的:
3 虚继承 - 图4
我们通过指针访问B1类对象的内存,以验证上面的C++对象模型:

  1. int main()
  2. {
  3. B1 a;
  4. cout <<"B1对象内存大小为:"<< sizeof(a) << endl;
  5. //取得B1的虚函数表
  6. cout << "[0]B1::vptr";
  7. cout << "\t地址:" << (int *)(&a)<< endl;
  8. //输出虚表B1::vptr中的函数
  9. for (int i = 0; i<2;++ i)
  10. {
  11. cout << " [" << i << "]";
  12. Fun fun1 = (Fun)*((int *)*(int *)(&a) + i);
  13. fun1();
  14. cout << "\t地址:\t" << *((int *)*(int *)(&a) + i) << endl;
  15. }
  16. //[1]
  17. cout << "[1]vbptr " ;
  18. cout<<"\t地址:" << (int *)(&a) + 1<<endl; //虚表指针的地址
  19. //输出虚基类指针条目所指的内容
  20. for (int i = 0; i < 2; i++)
  21. {
  22. cout << " [" << i << "]";
  23. cout << *(int *)((int *)*((int *)(&a) + 1) + i);
  24. cout << endl;
  25. }
  26. //[2]
  27. cout << "[2]B1::ib1=" << *(int*)((int *)(&a) + 2);
  28. cout << "\t地址:" << (int *)(&a) + 2;
  29. cout << endl;
  30. //[3]
  31. cout << "[3]值=" << *(int*)((int *)(&a) + 3);
  32. cout << "\t\t地址:" << (int *)(&a) + 3;
  33. cout << endl;
  34. //[4]
  35. cout << "[4]B::vptr";
  36. cout << "\t地址:" << (int *)(&a) +3<< endl;
  37. //输出B::vptr中的虚函数
  38. for (int i = 0; i<2; ++i)
  39. {
  40. cout << " [" << i << "]";
  41. Fun fun1 = (Fun)*((int *)*((int *)(&a) + 4) + i);
  42. fun1();
  43. cout << "\t地址:\t" << *((int *)*((int *)(&a) + 4) + i) << endl;
  44. }
  45. //[5]
  46. cout << "[5]B::ib=" << *(int*)((int *)(&a) + 5);
  47. cout << "\t地址: " << (int *)(&a) + 5;
  48. cout << endl;

运行结果:
3 虚继承 - 图5
这个结果与我们的C++对象模型图完全符合。这时我们可以来分析一下虚表指针的第二个条目值12的具体来源了:

第二、第三…个条目依次为该类的最左虚继承父类、次左虚继承父类…的内存地址相对于虚基类表指针的偏移值。

在我们的例子中,也就是B类实例内存地址相对于vbptr的偏移值,也即是:[4]-[1]的偏移值,结果即为12,从地址上也可以计算出来:007CFDFC-007CFDF4结果的十进制数正是12。现在,我们对虚基类表的构成应该有了一个更好的理解。

3 虚拟菱形继承

如果我们有如下继承层次:

  1. class B{...}
  2. class B1: virtual public B{...}
  3. class B2: virtual public B{...}
  4. class D : public B1,public B2{...}

类图如下所示:
3 虚继承 - 图6
菱形虚拟继承下,最派生类D类的对象模型又有不同的构成了。在D类对象的内存构成上,有以下几点:

  • 在D类对象内存中,基类出现的顺序是:先是B1(最左父类),然后是B2(次左父类),最后是B(虚祖父类)
  • D类对象的数据成员id放在B类前面,两部分数据依旧以0来分隔。
  • 编译器没有为D类生成一个它自己的vptr,而是覆盖并扩展了最左父类的虚基类表,与简单继承的对象模型相同。
  • 超类B的内容放到了D类对象内存布局的最后。

菱形虚拟继承下的C++对象模型为:
3 虚继承 - 图7
下面使用代码加以验证:

  1. int main()
  2. {
  3. D d;
  4. cout << "D对象内存大小为:" << sizeof(d) << endl;
  5. //取得B1的虚函数表
  6. cout << "[0]B1::vptr";
  7. cout << "\t地址:" << (int *)(&d) << endl;
  8. //输出虚表B1::vptr中的函数
  9. for (int i = 0; i<3; ++i)
  10. {
  11. cout << " [" << i << "]";
  12. Fun fun1 = (Fun)*((int *)*(int *)(&d) + i);
  13. fun1();
  14. cout << "\t地址:\t" << *((int *)*(int *)(&d) + i) << endl;
  15. }
  16. //[1]
  17. cout << "[1]B1::vbptr ";
  18. cout << "\t地址:" << (int *)(&d) + 1 << endl; //虚表指针的地址
  19. //输出虚基类指针条目所指的内容
  20. for (int i = 0; i < 2; i++)
  21. {
  22. cout << " [" << i << "]";
  23. cout << *(int *)((int *)*((int *)(&d) + 1) + i);
  24. cout << endl;
  25. }
  26. //[2]
  27. cout << "[2]B1::ib1=" << *(int*)((int *)(&d) + 2);
  28. cout << "\t地址:" << (int *)(&d) + 2;
  29. cout << endl;
  30. //[3]
  31. cout << "[3]B2::vptr";
  32. cout << "\t地址:" << (int *)(&d) + 3 << endl;
  33. //输出B2::vptr中的虚函数
  34. for (int i = 0; i<2; ++i)
  35. {
  36. cout << " [" << i << "]";
  37. Fun fun1 = (Fun)*((int *)*((int *)(&d) + 3) + i);
  38. fun1();
  39. cout << "\t地址:\t" << *((int *)*((int *)(&d) + 3) + i) << endl;
  40. }
  41. //[4]
  42. cout << "[4]B2::vbptr ";
  43. cout << "\t地址:" << (int *)(&d) + 4 << endl; //虚表指针的地址
  44. //输出虚基类指针条目所指的内容
  45. for (int i = 0; i < 2; i++)
  46. {
  47. cout << " [" << i << "]";
  48. cout << *(int *)((int *)*((int *)(&d) + 4) + i);
  49. cout << endl;
  50. }
  51. //[5]
  52. cout << "[5]B2::ib2=" << *(int*)((int *)(&d) + 5);
  53. cout << "\t地址: " << (int *)(&d) + 5;
  54. cout << endl;
  55. //[6]
  56. cout << "[6]D::id=" << *(int*)((int *)(&d) + 6);
  57. cout << "\t地址: " << (int *)(&d) + 6;
  58. cout << endl;
  59. //[7]
  60. cout << "[7]值=" << *(int*)((int *)(&d) + 7);
  61. cout << "\t\t地址:" << (int *)(&d) + 7;
  62. cout << endl;
  63. //间接父类
  64. //[8]
  65. cout << "[8]B::vptr";
  66. cout << "\t地址:" << (int *)(&d) + 8 << endl;
  67. //输出B::vptr中的虚函数
  68. for (int i = 0; i<2; ++i)
  69. {
  70. cout << " [" << i << "]";
  71. Fun fun1 = (Fun)*((int *)*((int *)(&d) + 8) + i);
  72. fun1();
  73. cout << "\t地址:\t" << *((int *)*((int *)(&d) + 8) + i) << endl;
  74. }
  75. //[9]
  76. cout << "[9]B::id=" << *(int*)((int *)(&d) + 9);
  77. cout << "\t地址: " << (int *)(&d) +9;
  78. cout << endl;
  79. getchar();
  80. }

查看运行结果:
3 虚继承 - 图8