【多态】底层原理

图片名称

博主首页: 有趣的中国人

专栏首页: C++进阶


    本篇文章主要讲解 多态底层原理 的相关内容

      1. 多态原理

      1.1 虚函数表


      先看一下这段代码,计算一下sizeof(Base)是多少:

      class Base
      {
      public:
      	virtual void Func1()
      	{
      		cout << "Func1()" << endl;
      	}
      private:
      	int _b = 1;
      	char _ch = 'd';
      };
      

      我这里以32位机器为例:很多人会认为这里是8字节,但是肯定没那么简单,这里答案是12字节。为什么呢?

      在这里插入图片描述

      这里不但有两个成员变量,还有一个虚表指针:__vfptr: virtual function pointer

      在这里插入图片描述

      在一个含有虚函数的类中,一定会存在一个虚表指针,因为虚函数的地址会存放在虚表当中,那么派生类中的这个表都有什么呢?接着往下分析。


      上面的代码进行改造:

      1. 增加一个派生类Derive去继承Base
      2. Derive中重写Func1
      3. Base再增加一个虚函数Func2和一个普通函数Func3

      代码如下:

      class Base
      {
      public:
      	virtual void Func1()
      	{
      		cout << "Base::Func1()" << endl;
      	}
      	virtual void Func2()
      	{
      		cout << "Base::Func2()" << endl;
      	}
      	void Func3()
      	{
      		cout << "Base::Func3()" << endl;
      	}
      private:
      	int _b = 1;
      };
      class Derive : public Base
      {
      public:
      	virtual void Func1()
      	{
      			cout << "Derive::Func1()" << endl;
      	}
      private:
      	int _d = 2;
      };
      int main()
      {
      	Base b;
      	Derive d;
      	return 0;
      }
      

      通过监视窗口观察,发现基类和派生类虚表指针的地址是不同的
      在这里插入图片描述
      在这里插入图片描述

      通过观察,我们发现,基类的虚函数表会复制到派生类的虚函数表中,如果派生类虚函数进行了重写,那么对应的虚函数地址就会改变,新函数地址会覆盖掉基类中的地址,因此重写又叫做覆盖。

      因此,多态是如何保证父类的指针调用虚函数就访问父类,派生类的指针调用虚函数就访问派生类的呢?
      在编译阶段,编译器会检查语法,看是否满足多态的两个条件:

      1. 虚函数重写
      2. 父类的指针或者引用调用虚函数

      如果满足,就会在链接阶段直接在虚表找到对应的函数地址并调用;
      如果不满足,就会在编译阶段根据类型确定函数的地址。
      以下是不构成重写的情况:

      class Person {
      public:
      	// 这个函数没有进入虚函数表
      	void BuyTicket() { cout << "买票-全价" << endl; }
      
      private:
      	int _i = 1;
      };
      
      class Student : public Person {
      public:
      	virtual void BuyTicket() { cout << "买票-半价" << endl; }
      
      	int _j = 2;
      };
      
      void Func(Person* p)
      {
      	p->BuyTicket();
      }
      
      int main()
      {
      	Person Mike;
      	Func(&Mike);
      
      	Person p1;
      	Func(&p1);
      
      	Student Johnson;
      	Func(&Johnson);
      
      	return 0;
      }
      

      2. 打印虚表

      虚表本质上是一个函数指针数组,即是一个数组,存放的类型是函数指针类型,我们只需要知道函数指针数组的首地址就可以访问其中的所有元素了。
      我们同样知道__vfptr存放的就是首地址,取到他就好,这里可以用指针的强制转换。

      先看一下这段代码:

      class Base {
      public:
      	virtual void func1() { cout << "Base::func1" << endl; }
      	virtual void func2() { cout << "Base::func2" << endl; }
      private:
      	int a = 1;
      };
      
      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 = 2;
      };
      

      这里Derive中覆盖了Base中的func1,继承了Base中的func2fun3func4进入了虚表:
      在这里插入图片描述

      typedef void(*VFPTR)();
      void PrintVFT(VFPTR* vft)
      {
      	for (int i = 0; i < 4; ++i)
      	{
      		printf("%p->", vft[i]);
      		VFPTR v = vft[i];
      		(*v)();
      	}
      }
      int main()
      {
      	Base b;
      	Derive d;
      	VFPTR* ptr = (VFPTR*)(*((int*)(&d)));
      	PrintVFT(ptr);
      	return 0;
      }
      

      在这里插入图片描述

      相关推荐

      1. 底层实现原理和泛型的底层实现原理

        2024-04-23 08:10:04       39 阅读
      2. 【从编译器的角度看底层实现原理

        2024-04-23 08:10:04       31 阅读

      最近更新

      1. TCP协议是安全的吗?

        2024-04-23 08:10:04       17 阅读
      2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

        2024-04-23 08:10:04       16 阅读
      3. 【Python教程】压缩PDF文件大小

        2024-04-23 08:10:04       15 阅读
      4. 通过文章id递归查询所有评论(xml)

        2024-04-23 08:10:04       18 阅读

      热门阅读

      1. asio之地址

        2024-04-23 08:10:04       13 阅读
      2. 密码学系列1-安全规约

        2024-04-23 08:10:04       13 阅读
      3. JVM加载类的流程

        2024-04-23 08:10:04       13 阅读
      4. JVM中的堆和栈

        2024-04-23 08:10:04       11 阅读
      5. 掌控基础设施,加速 DevOps 之旅:IaC 深度解析

        2024-04-23 08:10:04       12 阅读
      6. Web 常见十大漏洞原理及利用方式

        2024-04-23 08:10:04       15 阅读
      7. 2024年深圳杯&东三省数学建模联赛赛题浅析

        2024-04-23 08:10:04       15 阅读
      8. STM32 J-LINK

        2024-04-23 08:10:04       14 阅读
      9. 张量堆叠函数torch.stack()

        2024-04-23 08:10:04       10 阅读