一、虚函数的工作原理
每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就会为这个类创建一个虚函数表(VTABLE)保存该类所有虚函数的地址,其实这个VTABLE的作用就是保存自己类中所有虚函数的地址,可以把VTABLE形象地看成一个函数指针数组,这个数组的每个元素存放的就是虚函数的地址。在每个带有虚函数的类 中,编译器秘密地置入一指针,称为vpointer(缩写为VPTR),指向这个对象的VTABLE。 当构造该派生类对象时,其成员VPTR被初始化指向该派生类的VTABLE。所以可以认为VTABLE是该类的所有对象共有的,在定义该类时被初始化;而VPTR则是每个类对象都有独立一份的,且在该类对象被构造时被初始化。
通过基类指针做虚函数调 用时(也就是做多态调用时),编译器静态地插入取得这个VPTR,并在VTABLE表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑发生。为每个类设置VTABLE、初始化VPTR、为虚函数调用插入代码,所有这些都是自动发生的,所以我们不必担心这些。
#include<iostream> class A class B : public A int main() system("pause"); |
毫无疑问,调用了B::fun1(),但是B::fun1()不是像普通函数那样直接找到函数地址而执行的。真正的执行方式是:首先取出pa指针所指向的对象的vptr的值,这个值就是vtbl的地址,由于调用的函数B::fun1()是第一个虚函数,所以取出vtbl第一个表项里的值,这个值就是B::fun1()的地址了,最后调用这个函数。因此只要vptr不同,指向的vtbl就不同,而不同的vtbl里装着对应类的虚函数地址,所以这样虚函数就可以完成它的任务,多态就是这样实现的。
而对于class A和class B来说,他们的vptr指针存放在何处?其实这个指针就放在他们各自的实例对象里。由于class A和class B都没有数据成员,所以他们的实例对象里就只有一个vptr指针。
含有虚函数的对象在内存中的结构如下:
class A { private: int a; int b; public: virtual void fun0() { cout<<"A::fun0"<<endl; } }; |