序 :以下测试和结论都是以单继承的基础。
问题1:虚表是什么,存在哪里,与类和其实例化对象有啥关联
众所周知,虚表是一个 虚表实际是一个个函数指针的集合,该集合是个数组 。这个可以从各种资料上得到的书面解释。那它存在哪里,与对象、类是什么关系。今天我们从代码层面上来一探究竟。
//第一个示例,定义个含有虚函数的类型,并用之示例化
class baseClass {
public:
virtual void fun1() {
std::cout << "base::fun1()" << std::endl;
}
virtual void fun2() {
std::cout << "base::fun2()" << std::endl;
}
void fun3() {
std::cout << "base::fun3() non-virtual" << std::endl;
}
protected:
int _b = 0;
};
int main()
{
baseClass base1;
baseClass base2;
int* pVFT_base2 = ((int*)&base2);
getchar();
return 0;
}
运行后监视两个实例化的对象,及base2首地址的内容
从图中明确看出 base1 base2 两个对象拥有了一样的虚表,也即两个对象同用了同一张虚表,而pVFT_base2定义的指针是base2的首地址,其里面的内容为虚表的指针的地址。
如此得出结论:
1、虚表一个函数指针的数组。
2、含虚函数的类的首个4字节的地址存的是一个指针,该指针内容及为虚表的起始地址,这一点铺垫了后面的测试。
3、 一个类会持有一个虚表,与实例化对象无关,同一个类的所有实例化的对象持有的虚表是同一个,和静态成员变量类似。
问题二:多态基于继承关系中虚表实现,那虚表在继承关系中是如何维护的。
在上面的例子里,添加个派生类,对父类不做任何动作,并也用之实例化一对象,代码如下
class baseClass {
public:
virtual void fun1() {
std::cout << "base::fun1()" << std::endl;
}
virtual void fun2() {
std::cout << "base::fun2()" << std::endl;
}
void fun3() {
std::cout << "base::fun3() non-virtual" << std::endl;
}
protected:
int _b = 0;
};
class deriveClass : public baseClass {
public:
virtual void fun5() {
std::cout << "deriveClass::fun5()" << std::endl;
}
protected:
int _d = 0;
};
int main()
{
baseClass base1;
baseClass base2;
deriveClass derive;
int* pVFT_base2 = ((int*)&base2);
getchar();
return 0;
}
再讲 示例话的derive加入的监视中,一探究竟。
现在在 deriveClass重载func2接口,再看一遍监视。
从两张图中对比可知。含有虚函数的类被继承后会有如下几个动作
1、派生类会从其父类(为啥是父类,而不是基类),拷贝其全部的虚表元素到自己的虚表里面
2、如果派生类重载 了接口,则会将自己虚表里面对应偏移的接口地址改写成重载后的接口地址
为验证是拷贝的父类还是基类,再加层继承,从deriveClass 派生出ThirsDeriveClass,不做任何实际动作,再实例化一对象。看监视器。
图中看出 fun2 是其父类的地址,而非基类的。上面的结论正确。但是奇怪的问题来了,fun5 也为虚函数,为啥没在虚表里面展示出来。难道还有另一张虚表存在么,这个猜想肯定是错的,一个类只有一个虚表,那真相是没展示出来。单func5确确实实存在在虚表里面,而且在虚表中的位置应该紧随继承而来的虚函数后面。下面我们来模拟下虚函数的调用,看看能不能通过虚表的偏移将func5调用起来。。来验证我们的猜想
#include "stdafx.h"
#include "iostream"
class baseClass {
public:
virtual void fun1() {
std::cout << "base::fun1()" << std::endl;
}
virtual void fun2() {
std::cout << "base::fun2()" << std::endl;
}
void fun3() {
std::cout << "base::fun3() non-virtual" << std::endl;
}
protected:
int _b = 0;
};
class deriveClass : public baseClass {
public:
virtual void fun2() {
std::cout << "deriveClass::fun2()" << std::endl;
}
virtual void fun5() {
std::cout << "deriveClass::fun5()" << std::endl;
}
protected:
int _d = 0;
};
class ThirsDeriveClass : public deriveClass {
public:
virtual void fun5() {
std::cout << "ThirsDeriveClass::fun5()" << std::endl;
}
};
typedef void (__stdcall *PVFT)(); //函数指针
void ImitateCall(baseClass& class1) {
PVFT* pVFT = (PVFT*)(*((int*)&class1));
while (*pVFT) {
(*pVFT)();
pVFT += 1;
}
std::cout << std::endl;
}
int main() {
baseClass base1;
baseClass base2;
deriveClass derive;
ThirsDeriveClass ThirsDerive;
int* pVFT_base2 = ((int*)&base2);
std::cout << "base call begin" << std::endl;
ImitateCall(base1);
std::cout << "ThirsDerive call begin" << std::endl;
ImitateCall(ThirsDerive);
std::cout << "derive call begin" << std::endl;
ImitateCall(derive);
getchar();
return 0;
}
运行的结果如果,确实可以通过编译调用到派生类自己的虚函数,故此印证了我们的猜测是正确的。