跟我一起学习C++虚函数--第二篇
上一篇,我们讨论了带有虚函数的对象的内存布局情况。这一篇,主要讨论带有虚函数的类在单一继承情况下的内存布局情况。
还是从例子入手:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <iostream> 2 3 using namespace std; 4 5 class Point 6 { 7 public: 8 int x(); 9 virtual void y(){cout << "Point y" << endl;}; 10 virtual void print1(){cout << "Point print" << endl;}; 11 int _x; 12 }; 13 14 class Point2:public Point 15 { 16 public: 17 virtual void y(){ cout << "Point2 y" << endl;}; 18 virtual void print2(){cout << "Point2 print" << endl;}; 19 int _y; 20 }; 21 22 int main() 23 { 24 Point *p = new Point(); 25 Point2 *p2 = new Point2(); 26 27 /*first part*/ 28 cout << p2 << "\t" << &p2->_x << "\t" << &p2->_y << endl; 29 30 /*second part*/ 31 typedef void (* Func)(void); 32 Func pFunc = (Func)*((int *)*(int *)(p2)); 33 pFunc();//输出:Point2 y 34 pFunc = (Func)*((int *)*(int *)(p2)+1); 35 pFunc();//输出:Point print 36 pFunc = (Func)*((int *)*(int *)(p2)+2); 37 pFunc();//输出:Point2 print 38 39 return 0; 40 }
对与这个例子,我们从两部分讨论。
第一部分:继承情况下,对象本身(除虚函数表外)的内存布局。
从代码中first part的输出情况,我们可以看出对象内存布局情况如下图所示:
虚指针始终在对象的起始地址处,接下来是父类的成员变量,最后是子类自己的成员变量。
第二部分:继承情况下,虚函数表的内存布局。
从代码second part的输出情况,我们可以看出虚函数表的内存布局如下图所示:
编译器之所以这样安排父类和子类中虚函数的位置,说白了就是为了实现对父子类虚函数的动态绑定,因为对虚函数是通过虚表的槽位来进行调用的。举例说明:
比如调用void y();
编译器可能把代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 Point *p = new Point(); 2 p->y(); 3 p = new Point2(); 4 p->y();
把所有对y()函数的调用都变成 p->vptr->vptbl[0];而Point和Point2对象的槽位0中分别是它们自己的void y()函数,因此就实现了动态绑定。类似的,对子类没有覆盖的虚函数,也可以通过同样的槽位进行调用;对子类有而父类没有的虚函数,子类对象可以用自己独有的槽位来进行调用。
参考文献:
1.《深度探索C++对象模型》