C++高级特性:虚函数与多态的实现原理(十三)

news2024/11/17 10:30:31
1、虚函数表和虚函数表指针
  • 如果一个类存在virtual关键字函数或者继承的基类中存在virtual关键字的函数,那么该类的就会存在vptr和一个vtable

  • vptr虚函数表指针全称virtual table pointer、vtable是虚函数表virtual table的缩写。

    class A{
    public:
        ~A();
    };
    
    class B{
    public:
        virtual void func();
        virtual ~B();
    };
    
    class C: public B{
    
    };
    int main() {
        std::cout << "sizeof(A) = " << sizeof(A) << std::endl;          // sizeof(A) = 1
        std::cout << "sizeof(B) = " << sizeof(B) << std::endl;          // sizeof(B) = 8
        std::cout << "sizeof(C) = " << sizeof(C) << std::endl;          // sizeof(C) = 8
    
        std::cout << "Hello, World!" << std::endl;
        return 0;
    }
    
  • 空类的大小为1字节,而存在虚函数(虚析构函数或其他虚函数)那么就会为该类生成一个vptr虚函数表指针和一个vtable虚函数表

    • 32位机器:vptr大小4字节
    • 64为机器:vptr大小8字节
    • 这个大小其实就是虚函数表指针vptr的大小
2、vptr和vtable的生成与存储
2.1、vtable虚函数表的生成
  • vtable虚函数表是在编译阶段生成的,存放在代码区的.rodata常量存储区只读数据段中,其中存储所有虚函数的函数地址
  • 所有函数的具体实现方式都会在编译阶段生成机器指令存储在代码区的.text代码段,并且都有一个地址,虚函数表里会存储所有的虚函数的地址
  • 一个类最多只有一张虚函数表(存在.rodata段),一个类也只有一套函数体(存在.text段)
2.2、vptr虚函数指针的创建
  • vptr的创建是在new的阶段,当new一个对象进行初始化调用构造函数时会给vptr指针进行赋值让其指向“对应的”vtable虚函数表

  • 如果没有构造函数,编译器会生成默认的空构造函数

  • 继承状态下:

    • 首先调用基类的构造函数进行构造,把基类的vtable地址赋值给vptr

    • 其次调用本类的构造函数进行构造,如果本类存在vtable那么将本类的vtable地址赋值给vptr

    • 这其实就是动态多态的实现原理
      在这里插入图片描述

3、多态的实现原理
  • C++多态是有两种形式:
    • 静态多态:早绑定(静态绑定),在编译期间做
    • 动态多态:晚绑定(动态绑定),在运行期间做
3.1、静态多态原理

静态多态的实现手段主要通过函数重载技术手段来实现,它会在编译期间就确定

  • 函数重载:

    • 函数名相同、参数个数不一样
    • 函数名相同、参数类型不一样
  • 函数重载原理:通过函数名修饰来实现

    • 预编译:去除空格、注释等;把头文件当中的函数声明拷贝到源文件当中,避免编译过程中的词法、语法分析找不到函数定义
    • 编译:语法分析,同时进行符号汇总(函数名)
    • 汇编:生成函数名到函数地址的映射,方便后续通过函数名找到函数定义位置,从而执行函数
    • 链接:将多个文件中的符号表汇总合并,地址回填等
int sum(int a, int b)
{
    return a + b;
}

double sum(double a, double b)
{
    return a + b;
}

例如上面这段代码经过g++ -c main.cpp -o main.c编译过程后进行反汇编生成出来的代码和符号表如下

// 符号表
指令:objdump -t main.c> character_table.txt
0000000000000051 g     F .text	0000000000000014 _Z3sumii
0000000000000065 g     F .text	000000000000001a _Z3sumdd
    

// 反汇编指令
指令:objdump -DC main.c> decompiler_code.txt    
0000000000000051 <sum(int, int)>:
.....

0000000000000065 <sum(double, double)>:
....
  • 可以看到_Z3sumii、_Z3sumdd分别表示sum的两个重载类型:int int参数和double double参数,存放在.text段
  • 而反汇编指令也可以看到类似的情形。
3.2、动态多态原理
class A {
public:
    A() = default;
    virtual void func();
    virtual ~A();
};

class B: public A {
public:
    B() = default;
    virtual void func() override;
    virtual ~B();
};

void test2()
{
    A *a = new A();             // 基类自身
    A *b = new B();             // 多态
    a->func();                  // A::func()
    b->func();                  // B::func()
}
  • 对于这样一段代码,首先类A、类B的大小都是8字节(64位机器),其次每个类都有自己的vtable表

  • A* a = new A()时,a指针通过构造函数进行初始化,此时a内部的vptr指针会指向A的虚函数表vtable

  • A* b = new B()时,虽然表面类型是A,但是实际类型是B。

    • new B()构造时:首先调用父类A的构造函数,将vptr指向A类的vtable虚函数表
    • 接着调用自己的构造函数时,会将vptr的指向重新赋值指向自己的vtable虚函数表
  • 需要在拷贝构造和赋值时注意,进行深拷贝而不是浅拷贝。浅拷贝意味着两个对象的指针都共享同一个vptr,一个释放另外一个就会导致野指针。

  • 其次也可以通过手动获取vptr指针然后再获取地址表执行函数

    void test3()
    {
        typedef void (*Func)(void);
        A *a = new A();             // 基类自身
        A *b = new B();             // 多态
    
        long* vptr_a = (long *)*(long *)a;
        long* vptr_b = (long *)*(long *)b;
        Func f_a = (Func) vptr_a[0];
        Func f_b = (Func) vptr_b[0];
        f_a();                      // A::func()
        f_b();                      // B::func()
    }
    
    • 首先定义一个void Func(void )类型的函数指针
    • 将a、b对象强制转换为long类型的指针(8字节),该指针就是vptr,并且指向虚函数表的第一个
    • 然后进行解引用获取地址第一个函数的地址,最后执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1614646.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

随机森林(Random Forests)

通过5个条件判定一件事情是否会发生&#xff0c;5个条件对这件事情是否发生的影响力不同&#xff0c;计算每个条件对这件事情发生的影响力多大&#xff0c;写一个随机森林&#xff08;Random Forests&#xff09;模型程序,最后打印5个条件分别的影响力。 ChatGPT 下面是一个使…

书生·浦语大模型实战营Day04OpenXLab 部署

书生浦语大模型实战营Day04OpenXLab 部署 如何在 OpenXLab 部署一个 InternLM2-7B chat 的应用。 OpenXLab浦源平台介绍 OpenXLab 浦源平台以开源为核心&#xff0c;旨在构建开源开放的人工智能生态&#xff0c;促进学术成果的开放共享。OpenXLab面向 AI 研究员和开发者提供…

微电子领域常见概念(五)界面结合能

微电子领域常见概念&#xff08;五&#xff09;界面结合能 界面结合能&#xff0c;也称为界面能或界面自由能&#xff0c;是描述两种不同材料接触时在它们的交界面上存在的特殊能量状态的物理量。在材料科学中&#xff0c;界面结合能是一个重要的概念&#xff0c;因为它直接影响…

【机器学习】特征筛选:提升模型性能的关键步骤

一、引言 在机器学习领域&#xff0c;特征筛选是一个至关重要的预处理步骤。随着数据集的日益庞大和复杂&#xff0c;特征的数量往往也随之激增。然而&#xff0c;并非所有的特征都对模型的性能提升有所贡献&#xff0c;有些特征甚至可能是冗余的、噪声较大的或者与目标变量无关…

STM32定时器编码器模式

定时器编码器模式&#xff1a; Timer -mode Cubemx配置项&#xff1a; 定时器编码模式选择&#xff1a; Encode: mode: TI1: 通道1上升沿使计数器1 TI2: 通道2上升沿使计数器1 TI1 and TI2: 1 和 2 都会1 EX: 获取 编码器正反转数值 数值demo&#xff1a; int Read_Spee…

claude3国内注册

claude3国内注册 Claude 3 作为大型语言模型的强大之处在于其先进的算法设计和大规模训练数据的应用&#xff0c;能够执行复杂和多样化的任务。以下是 Claude 3 主要的强项&#xff1a; 接近人类的理解能力&#xff1a;Claude 3 能够更加深入地理解文本的含义&#xff0c;包括…

7.Prism框架之对话框服务

文章目录 一. 目标二. 技能介绍① 什么是Dialog?② Prism中Dialog的实现方式③ Dialog使用案例一 (修改器)④ Dialog使用案例2(异常显示窗口) 一. 目标 1. 什么是Dialog?2. 传统的Dialog如何实现?3. Prism中Dialog实现方式4. 使用Dialog实现一个异常信息弹出框 二. 技能介…

python编写一个简单的课时记录系统

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 使用Python创建一个简单的课时记录系统 在学习过程中&#xff0c;跟踪课时的进度是非常重要…

LeetCode in Python 69. Sqrt(x) (x的平方根)

求x的平方根&#xff0c;第一想法可能是遍历0&#xff5e;x&#xff0c;求其平方&#xff0c;找到或且但其时间复杂度为O(n)&#xff0c;或是想到遍历0&#xff5e;M即可&#xff0c;其中M x // 2&#xff0c;将时间复杂度降至O()。本文利用二分思想&#xff0c;给出一种时间复…

python--pyQt5 进度条:QProgressBar

https://www.cnblogs.com/itwangqiang/articles/14959401.html https://blog.csdn.net/weixin_43990846/article/details/123880081 进度条用于向用户指示操作的进度&#xff0c;并向他们保证应用程序仍在运行 例 1 import sys from PyQt5.QtWidgets import QApplication, QWi…

(十六)C++自制植物大战僵尸游戏的宏定义讲解

植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/uzrnw 宏定义 在游戏代码中为了方便,定义了许多宏定义。使用宏定义简化代码并提高可读性。下面将讲解游戏中用到的宏定义。 代码位置 代码所在位置是Class\Scenes\GameScene文件夹中。具体如下图所示。 Define.h …

尝试给笔记本超频

超频&#xff08;英语&#xff1a;overclocking&#xff09;是把一个电子配件的时脉速度提升至高于厂方所定的速度运作&#xff0c;从而提升性能的方法&#xff0c;但此举有可能导致该配件稳定性以及配件寿命下降。 笔记本配置为&#xff1a; 处理器 AMD Ryzen 7 7730U wit…

学习部分排序,插入排序,冒泡排序以及希尔排序

1.插入排序 <1>.首先我们举个例子 我们要把6进行前面的插入&#xff0c;那我们要进行比较&#xff0c;首先确定一个end的指针&#xff0c;然后他指向的数字就是我们需要比较的&#xff0c;如果end指向的数比我们end1 的大的话&#xff0c;那我们就往前挪一个&#xff0c…

四六级英语听力考试音频无线发射系统在安顺学院的成功应用分析

四六级英语听力考试音频无线发射系统在安顺学院的成功应用分析 由北京海特伟业科技任洪卓发布于2024年4月22日 安顺学院为了提高学生的外语听力水平&#xff0c;并确保英语四六级听力考试的稳定可靠进行&#xff0c;决定对传统的英语听力音频传输系统进行改造&#xff0c;以提供…

【YOLOv9】实战二:手把手教你使用TensorRT实现YOLOv9实时目标检测(含源码)

‍‍&#x1f3e1;博客主页&#xff1a; virobotics(仪酷智能)&#xff1a;LabVIEW深度学习、人工智能博主 &#x1f384;所属专栏&#xff1a;『LabVIEW深度学习实战』 &#x1f4d1;上期文章&#xff1a;『【YOLOv9】实战一&#xff1a;在 Windows 上使用LabVIEW OpenVINO工具…

gitlab 16.x - ERR unknown command ‘HELLO‘

现象 gitlab部分操作报错500。通过Rails日志发现以下报错&#xff1a; 报错&#xff1a; RedisClient::CommandError ERR unknown command HELLO {"severity": "ERROR","time": "2024-04-22T02:50:16.906Z","correlation_id&quo…

3667B芯茂微SOP7封装5V1A 5W适配器/充电器芯片

3667B是一款高度集成的隔离型适配器和充电器的自供电PSR控制芯片&#xff0c;外部设计极其简单。LP3667 固定原边峰值电流&#xff0c;通过变压器原副边匝比来设置输出恒流点&#xff1b;通过设定一个FB 电阻来设置输出恒压点。为了实现系统成本的简化&#xff0c;LP3667 内置启…

CSS基础常用属性之字体属性(如果想知道CSS的字体属性知识点,那么只看这一篇就足够了!)

前言&#xff1a;在我们学习CSS的时候&#xff0c;主要学习选择器和常用的属性&#xff0c;而这篇文章讲解的就是最基础的属性之一——文字属性。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSDN博客 废话不多说&#xff0c;让我们直…

删除二叉树的子树:假设二叉树中的结点均不相等,采用二叉链存储,设计递归算法删除根结点值为x的子树。(C语言)

目录 实验内容&#xff1a; 实验过程&#xff1a; 1.算法设计 2.程序清单 3.复杂度分析 4.运行结果 实验内容&#xff1a; 删除二叉树的子树:假设二叉树中的结点均不相等&#xff0c;采用二叉链存储&#xff0c;设计递归算法删除根结点值为x的子树。 实验过程&#xff1…

web前端(简洁版)

0. 开发环境 && 安装插件 这里我使用的是vscode开发环境 Auto Rename Tag是语法自动补齐view-in-browser是快速在浏览器中打开live server实时网页刷新 1. HTML 文件基本结构 <html><head><title>第一个页面</title></head><body&g…