• C++ | 虚表的写入时机


    虚表

    在C++的多态机制中,使用了 virtual 关键字声明的函数称之为虚函数,每个有虚函数的类或者虚继承的子类,编译器都会为它生成一个虚拟函数表(简称:虚表,以下用 vftable表示),表中的每一个元素都指向一个虚函数的地址。

    我们都知道在C++中对象生成有两个步骤:
    1、分配内存空间
    2、调用构造函数
    多态机制发生在运行阶段,也就是对象生成阶段。那么问题就来了,虚表(编译阶段生成)是什么时候被写入到对象中的呢?

    1、探究虚函数表写入时机

    目前有两种假设

    • 假设一:虚表写入发生在在构造函数之前
    • 假设二:虚表写入发生在在构造函数之后

    这里设计了一段代码来探究虚表具体的写入时机

    #include <iostream>
    
    class Base		//定义基类
    {
    public:
    	Base(int a) :ma(a) 
    	{
    		std::memset(this, 0, sizeof(this));
    	}
    	virtual void Show()
    	{
    		std::cout << "Base: ma = " << ma << std::endl;
    	}
    protected:
    	int ma;
    };
    
    
    int main()
    {
    	Base* pb = new Base(10);		
    	pb->Show();
    	return 0;
    }
    
    实验原理:
    • 在构造函数中使用 memset() 函数,把对象中的所有值赋值为0,如果虚表在构造函数之前被写入,将会是以下过程:
      对象开辟空间后,构造函数调用之前,对象中 vfptr==》vftable 虚表已经被写入对象(虚表指针中存入虚表的地址)
      在这里插入图片描述
      调用构造函数后,std::memset(this, 0, sizeof(this)) 将对象全部赋值为0,vfptr==》NULL
      在这里插入图片描述
      推测:如果虚表在构造函数之前被写入,那么,pb->Show() 将无法调用,程序崩溃

    运行测试:
    在这里插入图片描述
    现在,我们在分析如果虚表在构造函数之后被写入。
    那么,在调用构造函数后(ma=0. vfptr==》NULL),虚表会被写入对象,即 vfptr==》vftable 。根据上面的运行结果显示,显然不是这样的。
    在这里插入图片描述

    结论:

    虚表(vftable)在编译阶段生成,对象内存空间开辟以后,写入对象中的 vfptr,然后调用构造函数。即:虚表在构造函数之前写入

    2、虚表的二次写入

    先别急着下结论,在上面的实验中我们只测试了基类,没有测试派生类。虚表可不只有一张,在它的继承类中也存在一份虚表,因此我们接下来再做一个实验:

    #include <iostream>
    
    class Base		//定义基类
    {
    public:
    	Base(int a) :ma(a) 
    	{
    		std::memset(this, 0, sizeof(this));
    	}
    	virtual void Show()
    	{
    		std::cout << "Base: ma = " << ma << std::endl;
    	}
    protected:
    	int ma;
    };
    ///////////////////////////////////////////////////////////
    // 以下为新添加部分
    class Deriver : public Base		//派生类
    {
    public:
    	Deriver(int b) :mb(b), Base(b) {}	// 构造函数什么都不做
    	void Show()
    	{
    		std::cout << "Deriver: mb = " << mb << std::endl;
    	}
    protected:
    	int mb;
    };
    ////////////////////////////////////////////////////////////
    int main()
    {
    	Base* pb = new Deriver(10);		
    	pb->Show();
    	return 0;
    }
    
    

    这次加上派生类,并且依然让基类的构造中进行 memset() 操作,让我们来看看运行结果:
    在这里插入图片描述
    过程分析:(如下图所示)由于在子类的构造函数中没有做任何事,因此第③步虚表指向并没有改变,最后正常输出Deriver::mb 的内容。
    在这里插入图片描述
    从这里也可以看出多态实现的原理。每个类只有一张虚表,类的对象共享类的虚表,并且通过虚表的二次写入机制,让每个对象的虚表指针都能准确的指向到自己类的虚表,为实现动多态提供支持。


    最后在附上一张动多态原理图:
    在这里插入图片描述
    在调用虚函数时,如pb->Show() ,通过对象的vfptr 查询虚表,在虚表中保存着 Deriver::Show() 的函数入口地址,从而实现父类指针访问子类对象的方法。

    也就是说,在调用成员函数时会根据调用函数的对象的类型来执行不同的函数。从而实现了去调同一函数,而产生了不同的行为,形成了多种状态的效果。

  • 相关阅读:
    faster rcnn学习(三)
    too many values to unpack (expected 2)
    RuntimeWarning: overflow encountered in ubyte_scalars
    C#中excel读取和写入
    C#中使用Sql对Excel条件查询
    IIS上部署MVC网站,打开后ExtensionlessUrlHandler-Integrated-4.0解决方法IIS上部署MVC网站,打开后500错误
    C#微信公众平台账号开发,从零到整,步骤详细。
    VS快捷键大全
    ASP.NET将文件写到另一服务器
    开放api接口签名验证
  • 原文地址:https://www.cnblogs.com/TaoR320/p/12680129.html
Copyright © 2020-2023  润新知