C++单继承和多继承中的虚函数表详解
C++单继承和多继承中的虚函数表详解
虚函数表(Virtual Function Table,简称VFT或VTBL)是C++实现多态性的重要机制之一。它是一个函数指针数组,用于存储类的虚函数地址。通过虚函数表,C++可以在运行时动态地调用正确的虚函数,实现多态性。本文将深入探讨单继承和多继承中虚函数表的生成和使用。
在【C++】多态原理剖析-CSDN博客中,我们已经讲述了派生类的虚表生成过程:
1.单继承中的虚函数表
// 单继承中的虚函数表
class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
private:
int a;
};
class Derive :public Base {
public:
// 重写func1
virtual void func1() { cout << "Derive::func1" << endl; }
// 新增虚函数
virtual void func3() { cout << "Derive::func3" << endl; }
virtual void func4() { cout << "Derive::func4" << endl; }
private:
int b;
};
在监视窗口,观察派生类对象d的成员构造,发现派生类的虚函数表中只有重写的func1和继承的func2,没有派生类自己新增的虚函数,这实际上是VS编译器的机制,会隐藏派生类自己新增的虚函数
🍟解决方案1:观察内存窗口
🍟🍟解决方案2:打印虚表
🍩虚表实际上就是一个函数指针数组,而对象中的_vfptr是指向整个数组的指针(虚表指针),所以只要拿到这个虚表指针,遍历数组即可打印
🍩简单起见,我们这里定义的虚函数都是无返回值,无参的,所以这些函数的指针类型都为void ()(),即这个数组的类型为void ()()
// 函数指针类型重命名VF_Ptr
typedef void(*VF_Ptr)();
// 函数指针数组
void PrintVFTable(VF_Ptr vft[])
{
// 虚表的最后一个数据存放nullptr
for (int i = 0; vft[i] != nullptr; ++i)
{
printf("[%d]:%p->", i, vft[i]);
// 函数调用
vft[i]();
}
cout << endl;
}
🍪对于函数指针与函数指针的详细内容,可以参考【C语言】函数指针|函数指针数组|函数指针数组指针|如何理解?-CSDN博客
函数指针的定义方式如下例:
打印函数定义完成之后,只需要传递参数虚表指针,就可以打印虚表列表
📖Note:
虚表指针的取法:虚表指针_vfptr是存放在类的前四个字节中的,所以(int*)&b将一个Base指针强制转换为int指针,拿到前4个字节,(int*)&b指向的是_vfptr这个对象,我们需要拿到这个对象的值,因此要解引用,但是一个int指针解引用后的数据是一个int类型的,所以又要将这个int类型的值转换成数组指针类型,最终就得到了(VF_Ptr) (int)&b
2.多继承中的虚函数表
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2 {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d;
};
由监视窗口,可以发现,多继承时派生类继承了Base1和Base2的虚表,分别完成了对两个基类中虚函数func1的重写
但是由于VS编译器的机制,在监视窗口观察不到需表中派生类自己定义的虚函数,我们借助上边定义的打印虚表函数进行观察
typedef void(*VFPTR) ();
void test2()
{
Base1 b1;
//强制转换成void** 解引用之后是void* 在32位下是4byte 在64位下是8byte
//32位和64位机器下都能执行
PrintVFTable((VF_Ptr*)*(void**)&b1);
Base2 b2;
PrintVFTable((VF_Ptr*)*(void**)&b2);
Derive d;
// 取前四个字节,实际上拿到的是派生类中Base1的虚表指针
PrintVFTable((VF_Ptr*)*(void**)&d);
// 打印派生类中Base2的虚表
// 方法1: 跳过一个Base1对象的大小
//PrintVFTable((VF_Ptr*)*(void**)((char*)&d + sizeof(Base1)));
// 方法2:借助基类和派生类的赋值转换 拿到Base2切片的指针
Base2* ptr2 = &d;
PrintVFTable((VF_Ptr*)*(void**)ptr2);
}
🍪结论:
- 多继承时派生类自己的虚函数(不是重写的虚函数)放在继承的第一个基类部分的虚函数表中
本文原文来自CSDN