虚函数表
C++中虚函数是通过一张**虚函数表(Virtual Table)**来实现的,在这个表中,主要是一个类的虚函数表的地址表;这张表解决了继承、覆盖的问题。在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以当我们用父类的指针来操作一个子类的时候,这张虚函数表就像一张地图一样指明了实际所应该调用的函数。
C++编译器是保证虚函数表的指针存在于对象实例中最前面的位置(是为了保证取到虚函数表的最高的性能),这样我们就能通过已经实例化的对象的地址得到这张虚函数表,再遍历其中的函数指针,并调用相应的函数。
class Base {
public:
virtual void f() { cout << "Base::f()" << endl; }
virtual void g() { cout << "Base::g()" << endl; }
virtual void h() { cout << "Base::h()" << endl; }
};
typedef void(*Fun)(void);
int main()
{
Base b;
Fun pFun = NULL;
cout << (int*)(&b) << endl;
cout << *(int*)(&b) << endl;
cout << (Fun*)*(int*)(&b) << endl;
pFun = *(Fun*)*(int*)(&b);
pFun();
system("pause");
return 0;
}
解释:
(int*)(&b)
: 用转换int*
类型得到一个指向b首地址的指针(因为b是一个类,用int*得到地址是合理的),**首地址内容存放的是虚表指针,虚表指针指向的是虚函数表,虚函数表里面存的又是每个函数的指针,所以虚表指针类型是**或者Fun*类型
*(int*)(&b)
,这里解引用就可以得到虚表指针了,但是含有int*
类型,结果会出现错误,
所以(Fun*)*(int*)(&b)
转回来" ,就可以正确得到虚表指针指向的地址了,也就是虚函数表的首地址,虚函数表其实就是指针数组,所以得到首元素的地址
上一步解引用得到了虚表指针,再解引用虚表指针*(Fun*)*(int*)(&b)
就得到了虚函表首元素的内容了,就是第一个虚函数指针。
缺点
访问非public的虚函数#
父类非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数