• C++面向对象三大特性之一------多态(下)



    前言

    本章来了解关于单继承和多继承虚函数表,本章我们是进入内存中剖析虚函数表存的函数指针,内容可能有些复杂,请大家认真开完!


    正文开始

    一、单继承和多继承关系的虚函数表

    5.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:
    	virtual void func1() { cout << "Derive::func1" << endl; }
    	virtual void func3() { cout << "Derive::func3" << endl; }
    	virtual void func4() { cout << "Derive::func4" << endl; }
    private:
    	int b;
    };
    int main()
    {
    	Base b;
    	Derive d;
    
    	return 0;
    }
    

    在这里插入图片描述
    诶,我们可以看到虚函数的地址放入虚函数表中,但是对于Derive::func3和Derive::func4为什么没有存入自己的虚表中呢?
    这里是编译器的监视窗口故意隐藏了这两个函数,也可以认为是他的一个小bug。那么我们如何查看d的虚表呢?下面我们使用代码打印出虚表中的函数。
    这里我们需要用到函数指针去访问到内存中,取内存值,打印并调用,确认是否支持func3和func4

    //typedef void(*)() V_FPTR //不支持
    typedef void(*V_FPTR) ();
    //创建函数指针数组
    void PrintVTable(VFPTR vTable[])
    {
    	// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数
    	cout << " 虚表地址>" << vTable << endl;
    	//因为虚表的结尾存的是nullptr,所以可以作为他的判断条件
    	for (int i = 0; vTable[i] != nullptr; ++i)
    	{
    		printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
    		VFPTR f = vTable[i];
    		f();
    	}
    	cout << endl;
    }
    int main()
    {
    	Base b;
    	Derive d;
    	VFPTR * vTableb = (VFPTR*)(*(int*)&b);
    	PrintVTable(vTableb);
    	VFPTR* vTabled = (VFPTR*)(*(int*)&d);
    	PrintVTable(vTabled);
    	return 0;
    }
    

    思路就是取出b,d对象的头指针,就是虚表指针,前面我们说过虚表存的就是虚函数指针的指针数组,这个数组的最后放的是nullptr;
    1.取b的地址,强制类型转化为int*类型的指针
    2.再解引用取值得到了b对象前四个字节的值,这个值就是只想虚表的地址。
    3.在强制类型转换为V_FPTR函数指针类型,因为虚表就是一个存V_FPTR类型(虚函数指针类型)的数组。
    4.虚表指针传递为打印函数进行打印
    5.需要说明的是这个打印虚表的代码有时候会崩溃,可能是编译器没有对虚表处理干净,导致虚表最后没有存放nullptr,导致程序崩溃了。我们只需要点目录栏的重新生成解决方案即可!

    在这里插入图片描述
    在这里插入图片描述
    当然d也是同样的道理,我就不在这里验证啦!!

    5.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 d1;
    };
    typedef void(*VFPTR) ();
    void PrintVTable(VFPTR vTable[])
    {
    	cout << " 虚表地址>" << vTable << endl;
    	for (int i = 0; vTable[i] != nullptr; ++i)
    	{
    		printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
    		VFPTR f = vTable[i];
    		f();
    	}
    	cout << endl;
    }
    int main()
    {
    	Derive d;
    	VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
    	PrintVTable(vTableb1);
    	VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
    	PrintVTable(vTableb2);
    	return 0;
    }
    

    观察下图可以看出:多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中
    在这里插入图片描述
    在这里插入图片描述

    5.3. 菱形继承、菱形虚拟继承

    实际中我们不建议设计出菱形继承及菱形虚拟继承,一方面太复杂容易出问题,另一方面这样的
    模型,访问基类成员有一定得性能损耗。所以菱形继承、菱形虚拟继承我们的虚表我们就不看
    了,一般我们也不需要研究清楚,因为实际中很少用。如果好奇心比较强的童鞋,可以去看下面
    的两篇链接文章。
    1. C++ 虚函数表解析
    2. C++ 对象的内存布局


    总结

    (本章完)

  • 相关阅读:
    【C++】STL——vector(万字详解)
    前端实现搜索关键字高亮功能:
    从零开始学习Dubbo1——互联网项目目标与架构发展史
    PHP HTTP 函数
    使用 L293D 电机驱动器 IC 和 Arduino 控制直流电机
    WIinform 跨线程修改
    【AI视野·今日CV 计算机视觉论文速览 第251期】Thu, 21 Sep 2023
    鸿蒙开发 之 ArkUI状态管理
    IE浏览器攻击:MS11-003_IE_CSS_IMPORT
    基类、接口、抽象类的区别
  • 原文地址:https://blog.csdn.net/m0_61560468/article/details/126955738