在虚拟继承时,在派生类中会有偏移量表格指针,用来指向偏移量表格。在多态中如果派生类将基类的虚函数重写之后,那么基类中的虚函数和派生类中的虚函数在内存中的存储位置又是什么样的呢?
1.class Base
2.{
3.public:
4. virtual void Funtest1()
5. {
6. cout<<"Base::Funtest1()"<<endl;
7. }
8.
9. virtual void Funtest2()
10. {
11. cout<<"Base::Funtest2()"<<endl;
12. }
13.
14. int _a;
15.};
16.
17.class Derived: public Base
18.{
19.public:
20. virtual void Funtest1()
21. {
22. cout<<"Derived::Funtest1()"<<endl;
23. }
24.
25. virtual void Funtest3()
26. {
27. cout<<"Derived::Funtest2()"<<endl;
28. }
29.
30. int _b;
31.};
32.
33.
34.typedef void (*PVFT)();
35.void Print(Base& b)
36.{
37. PVFT* pvtf = (PVFT*)(*(int*)&b);
38. while(*pvtf)
39. {
40. (*pvtf)();
41. ++pvtf;
42. }
43.}
44.
45.void Funtest(Base& b)
46.{
47. //Base b;
48. Derived d;
49. d._a = 10;
50. d._b = 20;
51. b.Funtest1();
52. //Print(d);
53.}
54.
55.int main()
56.{
57. Base b;
58. Derived d;
59. Funtest(b);
60. Funtest(d);
61. return 0;
62.}
63.
要实现多态就必须用基类的指针或引用去调用派生类对象
先看看基类对象和派生类对象调用次序是什么样的(这里的打印函数是根据在内存中的虚函数地址位置来确定的,在VS2012中虚函数后面有保护间隔为00 00 00 00,所以可以根据这一特性来进行打印)
首先,当传的实参是基类对象时,在调用一次b.Funtest1()后,会去调基类里面的Funtest1,然后,直接再来打印基类的调用,当传的实参数派生类的对象时,由于在派生类中将Funtest函数重写了,然后加了自己的Funtest3函数,所以打印出来的就是派生类的1,基类的2,派生类的3
再来看看,他们在内存中的存放是怎么来存放的,一次就可以知道,虚函数单继承模型是什么样的了
这是基类对象的地址,然后进到第一行指针指向的内存空间中,在这块内存空间中存放的还是两个地址,我们并不知道这两个地址是什么意思,不妨再来看看派生类对象在内存中的存放。
先开始存放的是基类对象和派生类对象的赋值,可以看到基类对象在下面放着,然后进去第一行的指针指向的空间。进去之后还是和基类一样存放的是地址,而这次变为了3个。所以我们就可以大胆想象,这儿的指针实际上就是虚函数的地址,当为基类时,基类里有两个虚函数,这刚好是两个地址。当为派生类时,重写了Funtest1的虚函数,然后自己里还有一个Funtest3,所以是三个地址。派生类自己的虚函数存在于基类的虚函数后面。像这种指向一个存放虚函数地址的指针叫虚表指针,这块空间叫虚表。
所以可以大概知道单继承的多态模型
让Derived公有继承Base1和Base2
1.class Base1
2.{
3.public:
4. virtual void Funtest1()
5. {
6. cout<<"Base1::Funtest1()"<<endl;
7. }
8.
9. virtual void Funtest2()
10. {
11. cout<<"Base1::Funtest2()"<<endl;
12. }
13.
14. int _a1;
15.};
16.
17.class Base2
18.{
19.public:
20. virtual void Funtest3()
21. {
22. cout<<"Base2::Funtest3()"<<endl;
23. }
24.
25. virtual void Funtest4()
26. {
27. cout<<"Base2::Funtest4()"<<endl;
28. }
29.
30. int _a2;
31.};
32.class Derived: public Base1, public Base2
33.{
34.public:
35. virtual void Funtest1()
36. {
37. cout<<"Derived::Funtest1()"<<endl;
38. }
39.
40. virtual void Funtest3()
41. {
42. cout<<"Derived::Funtest3()"<<endl;
43. }
44.
45. virtual void Funtest5()
46. {
47. cout<<"Derived::Funtest5()"<<endl;
48. }
49.
50. virtual void Funtest6()
51. {
52. cout<<"Derived::Funtest6()"<<endl;
53. }
54. int _b;
55.
56.};
57.
58.
59.typedef void (*PVFT)();
60.void Print(Base1& b)
61.{
62. PVFT* pvtf = (PVFT*)(*(int*)&b);
63. while(*pvtf)
64. {
65. (*pvtf)();
66. ++pvtf;
67. }
68.}
69.
70.void Funtest(Base1& b)
71.{
72. //Base b;
73. Derived d;
74. d._a1 = 10;
75. d._a2 = 15;
76. d._b = 20;
77. Print(d);
78.}
79.
80.
81.int main()
82.{
83. Base1 b1;
84. Base2 b2;
85. Derived d;
86. //Funtest(b2);
87. cout<<endl;
88. Funtest(d);
89. return 0;
90.}
在派生类里将Base1里面的Funtest1重写,将Base2里面的Funtest3重写,然后自己里面添加Funtest5和Funtest6,先分别看看三个类在内存中是怎么存放的。
先看派生类,在最下面存放的是派生类里的成员变量的值,往上是0f(base2里面的成员变量的值),然后是一个地址,往上是0a(Base1里面的成员变量的值),再往上就是一个指针。
进入到Base2处指针指向的空间,里面存放的是2个地址,和单继承相似那这里存放的就是Base2里面虚函数的地址,然后进入最上面的指针指向的地址,里面存放的是4个地址,这4个地址分别是重写Funtest1后的地址,Funtest2的地址,Funtest5的地址和Funtest6的地址。
而派生类里面的虚函数存在于第一个继承的基类的虚函数后面。
所以虚函数多继承的一般模型可以描述为
1.class A
2.{
3.public:
4. virtual void Funtest1()
5. {
6. cout<<"A::Funtest1()"<<endl;
7. }
8.
9. virtual void Funtest2()
10. {
11. cout<<"A::Funtest2()"<<endl;
12. }
13.
14. int _a;
15.};
16.
17.class B1: public A
18.{
19.public:
20. virtual void Funtest1()
21. {
22. cout<<"B1::Funtest1()"<<endl;
23. }
24.
25. virtual void Funtest3()
26. {
27. cout<<"B1::Funtest3()"<<endl;
28. }
29.
30. int _b1;
31.};
32.
33.class B2: public A
34.{
35.public:
36. virtual void Funtest2()
37. {
38. cout<<"B2::Funtest2()"<<endl;
39. }
40.
41. virtual void Funtest4()
42. {
43. cout<<"B2::Funtest4()"<<endl;
44. }
45.
46. int _b2;
47.};
48.
49.class C: public B1, public B2
50.{
51.public:
52. virtual void Funtest3()
53. {
54. cout<<"C::Funtest3()"<<endl;
55. }
56.
57. virtual void Funtest4()
58. {
59. cout<<"C::Funtest4()"<<endl;
60. }
61.
62. virtual void Funtest5()
63. {
64. cout<<"C::Funtest5()"<<endl;
65. }
66.
67. int _c;
68.};
69.
70.void Funtest(B1& b)
71.{
72. C d;
73. d.B1::_a = 1;
74. d.B2::_a = 2;
75. d._b1 = 3;
76. d._b2 = 4;
77. d._c = 5;
78.}
79.
80.int main()
81.{
82. A a;
83. B1 b1;
84. B2 b2;
85. C c;
86. Funtest(c);
87. cout<<endl;
88. return 0;
89.}
虚函数的菱形继承和菱形继承很相似,不过给各个继承最前面加上一虚表指针
先来看看内存中的存放位置,看看和猜想的一样吗
果然和菱形继承时一样,而这里要提一下的就是在继承第一个的基类虚表指针中,将派生类中自己私有的虚函数加在了最后面
1.class A
2.{
3.public:
4. virtual void Funtest1()
5. {
6. cout<<"A::Funtest1()"<<endl;
7. }
8.
9. virtual void Funtest2()
10. {
11. cout<<"A::Funtest2()"<<endl;
12. }
13.
14. int _a;
15.};
16.
17.class B1:virtual public A
18.{
19.public:
20. virtual void Funtest1()
21. {
22. cout<<"B1::Funtest1()"<<endl;
23. }
24.
25. virtual void Funtest3()
26. {
27. cout<<"B1::Funtest3()"<<endl;
28. }
29.
30. int _b1;
31.};
32.
33.class B2:virtual public A
34.{
35.public:
36. virtual void Funtest2()
37. {
38. cout<<"B2::Funtest2()"<<endl;
39. }
40.
41. virtual void Funtest4()
42. {
43. cout<<"B2::Funtest4()"<<endl;
44. }
45.
46. int _b2;
47.};
48.
49.class C: public B1, public B2
50.{
51.public:
52. virtual void Funtest3()
53. {
54. cout<<"C::Funtest3()"<<endl;
55. }
56.
57. virtual void Funtest4()
58. {
59. cout<<"C::Funtest4()"<<endl;
60. }
61.
62. virtual void Funtest5()
63. {
64. cout<<"C::Funtest5()"<<endl;
65. }
66.
67. int _c;
68.};
69.
70.void Funtest(B1& b)
71.{
72. C d;
73. d._a = 1;
74. d._b1 = 3;
75. d._b2 = 4;
76. d._c = 5;
77. cout<<sizeof(d)<<endl;
78.}
79.
80.
81.int main()
82.{
83. A a;
84. B1 b1;
85. B2 b2;
86. C c;
87. Funtest(c);
88. cout<<endl;
89. return 0;
90.}
在刚刚的虚函数菱形继承上面加上虚拟继承后,还是不是和菱形虚拟继承相似,而偏移量表和虚表是如何存在的,那就继续在内存中查看一下,打开内存窗口
是不是看起来特别复杂,可以把内存中信息化为4部分,分别用红线圈起来。首先,可以知道基类还是在最下面,然后往上依次是派生类,再是两个基类。难点就是这几个指针,从下至上依次进入指针查看一下指针指向内存空间中的内容
进去之后里面存放的是两个地址,那么大概就可以知道这个指针变量存放的就是A的虚表,里面的两个地址分别是Funtest1和Funtest2的地址。
再往上的两个地址分别是0x0031dd7c和0x0031dd58,分别查看这两个指针指向的内容
左边对应的是0x0031dd58也就是上面的地址,里面存放的是一个地址,结合菱形继承大概可以知道这里面存放的就是B2里面添加的Funtest4,右边对应的是0x0031dd7c,里面存放的是两个地址,这个看起来是不是很眼熟,没错就是偏移量表,只不过在虚拟继承中第一行存放的是相对自己的偏移量,第二行存放的是相对于他的基类的偏移量。而现在再多态中,fcffffff对应的是-4,而这个地址的位置在虚表的下面,所以偏移量表里的第一行就是相对于他自己虚表的偏移量,第二行是相对于基类虚标的偏移量。
下来就是最上面的两个地址,现在应该可以肯定一个是偏移量,另一个是虚表,不过在虚表中,不仅仅存放了B1中的Funtest3而且还有派生类C中的Funtest5,这在每一个虚函数继承中都可以体现到(派生类中多的虚函数都是在第一个基类的虚函数后面存放)。
在虚函数虚拟继承中,如果给派生类加上构造函数,整个派生类的大小会多出4个字节来,在内存中查看到的是多了00 00 00 00