问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

C++单继承和多继承中的虚函数表详解

创作时间:
作者:
@小白创作中心

C++单继承和多继承中的虚函数表详解

引用
CSDN
1.
https://blog.csdn.net/m0_64538862/article/details/145483583

虚函数表(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

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号