虚函数表是C++类中存放虚函数的一张表,理解虚函数表对于理解多态很重要。
本次使用的编译器是VS2013,为了简化操作,不用去操作函数指针,我使用到了VS的CL编译选项来查看类的内存布局。
CL使用方法:
(1)在开始菜单中的vs目录下打开"Visual Studio Tools"目录,找到"VS2013 开发人员命令提示",打开它;
(2)将你要编译的文件放到该命令行对应的文件夹中。
(3)输入cl "文件名" /d1reportSingleClassLayout"类名"
(具体的使用方法可以参考以下博客:http://www.cnblogs.com/dsky/archive/2012/02/07/2340984.html)
例子:
#include <iostream> using namespace std; class C { public: int a; int b; }; int main() { return 0; }
输出结果:
(1)单个类的虚函数表
class C { public: virtual void foo() {} int m; };
结果分析:可以看见虚表处于类的最开始处,这是为了提高效率和正确查找虚函数。
(2)单继承
class Base { public: virtual void v1() {} int b; }; class Derived : public Base { public: virtual void v2() {} int d; };
结果分析:在类Derived的最前面是类Base,与我们所预期的一样。虚表并不是Base和Derived都有,而是只有Base有,Derived中的虚函数放在了Base里的虚表中。
(3)单继承 + 覆盖
class Base { public: virtual void v1() {} int b; }; class Derived : public Base { public: virtual void v1() {} virtual void v2() {} int d; };
结果分析:我们在Derived中覆盖了Base的v1,从结果可以看出虚表中原来的Base::v1变为了Derived::v1
(4)多继承
class Base1 { public: virtual void v1() {} int b1; }; class Base2 { public: virtual void v2() {} int b2; }; class Derived : public Base1, public Base2 { public: virtual void v3() {} int d; };
结果分析:两个基类各自有自己的虚表,而Derived的虚函数放在了第一张虚表中。
(5)虚继承
class VBase { public: virtual void vb1(){} int vb; }; class Base1 : virtual public VBase { public: virtual void v1() {} int b1; }; class Base2 : virtual public VBase { public: virtual void v2() {} int b2; }; class Derived : public Base1, public Base2 { public: virtual void v3() {} int d; };
结果分析:虚继承后,基类中多了一张虚类表(vbtable),该虚类表记录的是到虚类的偏移量,比如4+24 = 28。而虚函数表的情况一如我们之前讨论。再细看,我们发现虚基类并不作为继承类的一部分,而是由虚类表来找到。