虚函数表概述
C++ 的多态,使用动态绑定的技术,技术的核心是虚函数表(简称虚表),每个包含了虚函数的类都包含一个虚表,虚表是属于类的,而不是属于某个具体的对象,一个类只需要一个虚表即可。同一个类的所有对象共用一个虚表。
1.虚表指针
每个包含虚函数的类,都有一个虚表指针,虚表指针在数据结构的第一个,对象在创建时就有了这个指针,并且指向了该类的虚表。
#include <iostream>
using namespace std;
class TBase {
void fun1(){};
void fun2(){};
void fun3(){};
int x;
};
class VTBase {
virtual void fun1(){};
virtual void fun2(){};
virtual void fun3(){};
int x;
};
int main()
{
TBase tbase;
VTBase vtbase;
std::cout << sizeof(tbase) << endl;
std::cout << sizeof(vtbase) << endl;
}
- 如果是x64编译,默认是8字节对齐 输出为4,16
- 如果是x86编译,默认是4字节对齐 输出为4,8
我们知道函数是不会占用类的空间的,所以第一个Tbase类大小始终为4。含有虚函数的类,无论有多少个虚函数,都会增加一个虚表指针的大小(x64:8,x86:4)。
另外可以通过offsetof来看偏移,以x64编译器为例子:
cout << offsetof(VTBase, x) << endl;//输出值为8
x偏移为8,是我们结构体中可见的首个变量,所以前8个字节为虚函数表指针的大小,也是该类的第一个成员变量。
1.1剖析虚表指针的调用
这里还是以X64编译器举例
- 为了方便看代码,我们首先
typedef long long Vtpr_type;
typedef void (*VFunc)(void); //定义一个函数指针类型变量类型 VFunc
- 还是以VTBase类举例子,我们已知前8个字节为虚函数表指针,所以通过变量指向该地址
VTBase* v_base = new VTBase();
//将对象的首地址输出
Vtpr_type* vptr = nullptr;
//vptr是虚函数类的第一个成员变量,也是虚函数表的指针
memcpy(&vptr, v_base, sizeof(Vtpr_type));
- 虚函数的地址存储在了虚函数表中,虚函数表地址内的值,为虚函数的指针,我们依次获取虚函数表中的值,并执行
VFunc funa = (VFunc)(*vptr);
VFunc funb = (VFunc)(*(vptr + 1));
VFunc func = (VFunc)(*(vptr + 2));
funa();//VTBase::a()
funb();//VTBase::b()
func();//VTBase::c()
完整代码
#include <iostream>
using namespace std;
namespace {
class VTBase {
public:
virtual void a() { cout << "VTBase::a()" << endl; }
virtual void b() { cout << "VTBase::b()" << endl; }
virtual void c() { cout << "VTBase::c()" << endl; }
int x, y;
};
typedef long long Vtpr_type;
typedef void (*VFunc)(void); //定义一个函数指针类型变量类型 VFunc
} // namespace
int main()
{
//offsetof
VTBase vtbase;
std::cout << sizeof(vtbase) << endl;
cout << offsetof(VTBase, x) << endl;
cout << offsetof(VTBase, y) << endl;
/*
x偏移为8,y偏移为12
所以前8个字节为虚函数表的大小,也是该类的第一个成员变量
*/
VTBase* v_base = new VTBase();
//将对象的首地址输出
Vtpr_type* vptr = nullptr;
//vptr是虚函数类的第一个成员变量,也是虚函数表的指针
memcpy(&vptr, v_base, sizeof(Vtpr_type));
//虚函数的地址存储在了虚函数表中,虚函数表地址内的值,为虚函数的指针
VFunc funa = (VFunc)(*vptr);
VFunc funb = (VFunc)(*(vptr + 1));
VFunc func = (VFunc)(*(vptr + 2));
funa();
funb();
func();
delete v_base;
v_base = nullptr;
return 0;
}
1.2多个类共用一份虚函数表释义
void TestVtprValue() {
VTBase* v_base1 = new VTBase();
VTBase* v_base2 = new VTBase();
Vtpr_type* vptr1 = nullptr;
Vtpr_type* vptr2 = nullptr;
memcpy(&vptr1, v_base1, sizeof(Vtpr_type));
memcpy(&vptr2, v_base2, sizeof(Vtpr_type));
cout << vptr1 << endl;
cout << vptr2 << endl;
}
2.继承中的虚函数表
我们先定义一个派生类VTBaseExt,并且重写其中某一个虚函数
class VTBaseExt : public VTBase {
public:
virtual void b() { cout << "VTBaseExt::b()" << endl; }
};
void TestVtbExt()
{
VTBase* v_base = new VTBase();
Vtpr_type* vptr_base = nullptr;
memcpy(&vptr_base, v_base, sizeof(Vtpr_type));
cout << vptr_base << endl;
VTBaseExt* v_base_ext = new VTBaseExt();
Vtpr_type* vptr_ext = nullptr;
memcpy(&vptr_ext, v_base_ext, sizeof(Vtpr_type));
cout << vptr_ext << endl;
VFunc funa = (VFunc)(*(vptr_ext));
VFunc funb = (VFunc)(*(vptr_ext + 1));
VFunc func = (VFunc)(*(vptr_ext + 2));
funa();//VTBase::a()
funb();//VTBaseExt::b()
func();//VTBase::c()
}
此时执行,我们发现VTBase、VTBaseExt虚表大小均为4,但虚表数组中储存的第二个值发生了变化
2.1剖析派生类调用基类的虚函数是否为同一个
从上图可知是同一个
获取虚函数表数组存储的函数地址
void TestVtbExt()
{
VTBase* v_base = new VTBase();
Vtpr_type* vptr_base = nullptr;
memcpy(&vptr_base, v_base, sizeof(Vtpr_type));
cout << vptr_base << endl;
VTBaseExt* v_base_ext = new VTBaseExt();
Vtpr_type* vptr_ext = nullptr;
memcpy(&vptr_ext, v_base_ext, sizeof(Vtpr_type));
cout << vptr_ext << endl;
//获取虚函数表数组地址
for (int i = 0; i < 3; i++) {
cout << *(vptr_base + i) << " " << *(vptr_ext + i) << endl;
}
}
从图中可以看出来,只有被重写过的第二组是不一样的,其余的函数地址都没有变化
2.2基类指针指向派生类的虚函数表
调用代码释义:
void TestVtbPointer() {
//基类指针指向自己
VTBase* v_base = new VTBase();
Vtpr_type* vptr_base = nullptr;
memcpy(&vptr_base, v_base, sizeof(Vtpr_type));
cout << vptr_base << endl;
//派生类指针指向派生类
VTBaseExt* v_base_ext = new VTBaseExt();
Vtpr_type* vptr_ext = nullptr;
memcpy(&vptr_ext, v_base_ext, sizeof(Vtpr_type));
cout << vptr_ext << endl;
//基类指针指向派生类
VTBase* v_base_to_ext = new VTBaseExt();
Vtpr_type* vptr_to_ext = nullptr;
memcpy(&vptr_to_ext, v_base_to_ext, sizeof(Vtpr_type));
cout << vptr_to_ext << endl;
}
从结果输出来看,最终的基类指针指向派生类对象,虚函数表的指针还是指向了派生类的虚函数表
3.虚函数表总结
动态绑定的最终实现就是查虚函数表,每个对象的类对应了自己的虚函数表,虚函数表存储了与之对应的虚函数地址。
3.1动态绑定示意图
- 假设含有虚函数的基类VTBase ;
- VTBase 派生类VTBaseExt,重写了func_2;
- VTBaseExt派生类VTBaseFinal ,新增了func_4;
class VTBase {
public:
virtual void func_1() { cout << __FUNCTION__ << endl; }
virtual void func_2() { cout << __FUNCTION__ << endl; }
virtual void func_3() { cout << __FUNCTION__ << endl; }
int x;
};
class VTBaseExt : public VTBase {
public:
virtual void func_2() { cout << "VTBaseExt:: " << __FUNCTION__ << endl; }
};
class VTBaseFinal : public VTBaseExt {
public:
virtual void func_4() { cout << "VTBaseFinal:: " << __FUNCTION__ << endl; }
};