STL中 function 源码解析

news2024/9/21 22:30:04

1. function

本文基于 GCC 9.4

function 的作用就是将各种仿函数的调用统一起来;

1.1 类中非静态成员函数指针为什么是16字节

auto cptr = &A::myfunc;			类中非静态成员函数 ,其类型为 void (A::*)(int)
auto rptr = print_num;  		普通函数

对应汇编代码如下所示,可以看出,编译器为 this 指针预留了 8 字节的空间,此时没有绑定 this 指针,因此赋值为 0;

mov     QWORD PTR [rbp-32], OFFSET FLAT:A::myfunc(int)
mov     QWORD PTR [rbp-24], 0

mov     QWORD PTR [rbp-8], OFFSET FLAT:print_num(int, int)

此处也解释了为什么非静态成员函数无法作为 sort 等函数传入的仿函数指针;

1.2 存放对象

为简洁起见,以下代码中均删除了 const 对象或方法;

(1)function 中可以存放 lambda 表达式、重载()的结构体/类、函数指针、类中成员函数等对象,可以存储这些对象的指针,这些对象的调用方式是相似的;

函数指针调用方式 (*func_ptr)(arg);
重载()的结构体/类调用方式 (*object)(arg);

但这些对象的类型是不同的,如何存储这些对象是首要问题,如下 _Nocopy_types 所示,这里采用了 union 的设计方式,并且采用非常技巧的方式;

 union _Nocopy_types
 {
   void*       _M_object;							存放对象指针,例如 lambda 表达式、重载()的结构体/void (*_M_function_pointer)();					存放函数指针
   void (_Undefined_class::*_M_member_pointer)();	指向 类中的成员函数 的指针,注意其大小为 16 字节(静态成员函数指针仍为 8 字节)
 };

 union [[gnu::may_alias]] _Any_data
 {
   void*       _M_access()       { return &_M_pod_data[0]; } 特例化版本,存取union实际指针,可直接为 placement new 提供指针位置
   
   template<typename _Tp>									 函数模板版本,调用上述版本并进行必须的类型转换,可为 new 服务
     _Tp&													 注意:这里以引用形式返回
     _M_access()
     { return *static_cast<_Tp*>(_M_access()); }

   _Nocopy_types _M_unused;
   char _M_pod_data[sizeof(_Nocopy_types)];				为存放函数指针设置,便于获取union地址
 };

1.3 类之间的关系

在这里插入图片描述

_Functor 为模板参数

(1)在 _Base_manager 中

__stored_locally 为 Bool 值,用于判断是否需要在堆上存储,(一般是查看 _Functor 大小是否大于 16,因为其可能是一个类或 bind

若需要,则调用 new _Functor(),并将对象指针存放到 _Any_data 中;(这也是上图为什么使用虚线的原因)

否则,直接存放到 _Any_data

(2)初始化过程

根据 __stored_locally调用对应 _M_init_functor

调用 _M_access 存放对象

(3)调用过程

_M_invoker(_M_functor, std::forward<_ArgTypes>(__args)...);

_M_invoker 中,将上述过程转换为

此处 __functor 即为类中的 _M_functor
(*_M_get_pointer(__functor))(std::forward<_ArgTypes>(__args)...);

_M_get_pointer 函数就是获得实际对象指针,从而实现函数调用;

1.4 一些问题思考

(1)在 _M_get_pointer 中局部存储的情况为什么单独处理,为什么不能直接转换为_Functor*

 if _GLIBCXX17_CONSTEXPR (__stored_locally)						 第一种方式
   {
     const _Functor& __f = __source._M_access<_Functor>();		 获取对象本身
     return const_cast<_Functor*>(std::__addressof(__f));
   }
 else // have stored a pointer									
   return __source._M_access<_Functor*>();						 第二种方式

这里的最开始猜测是,若是分配在堆区,那么局部存储的肯定是指针;

而若是局部存储,局部存储的可能是一个对象(例如,一个结构体),

std::function<void(int)> f_display = print_num;					存储函数指针
std::function<void(int)> f_display_obj = PrintNum();			存储结构体

(2)进一步调试

如果存储的是对象,我们想要的其实是 &_M_pod_data[0] ,之后对其解引用就可以获得实际对象,

如果存储的堆区指针,则&_M_pod_data[0] 中存储的为堆区指针,我们需要的是*&_M_pod_data[0]

因此第一种方式获取的是, &_M_pod_data[0],适用于对象;

第二种方式获取的是,*&_M_pod_data[0],适用于堆区指针;

_Functor*&	_M_access(){ 
	return *static_cast<_Functor**>(_M_access()); 
}

auto tmp = static_cast<_Tp*>(myaccess());  此步的转换获取了指向对象的指针
_Tp tmp1 = (*tmp);						   取对象,进行转换,此步相当于获取对象中存储的内容,因此是错误的;

(3)之前一直很疑惑,为什么局部存储普通函数指针的时候,也要使用第一种方式?

之前感觉这种存储方式,最后需要二重取引用,怎么也与当前一重取引用对不上;

后来发现函数指针的调用有两种方式

  void*(*rig)();
  rig = myaccess;
  (*rig)();		
  rig();			这种方式与上述等价

而且从汇编代码来看,这两种方式生成的汇编代码都是一样的

(4)为何此处使用addressof 而不用取地址 & 符号

重载 & 描述符后,取出的地址与 this 指针不一定一致

可参考 https://en.cppreference.com/w/cpp/memory/addressof 中的示例;

1.5 总结

由上述分析来看,本质上来讲,gcc版本的实现中std::function 就是一个 固定大小的 字符数组,若该字符数组能够存放对象,则将其存放到此处,否则,在堆区创建对象,在此处存放对象指针;

1.6 参考

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

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

相关文章

MyBatis-Plus 实用工具:SqlHelper 让你的数据库操作更得心应手

一、SqlHelper是什么&#xff1f; SqlHelper 是MyBatis-Plus的一款SQL 辅助工具类&#xff0c;提供了一些常用的方法&#xff0c;简便我们的操作&#xff0c;提高开发效率。文档 二、示例代码 public class SqlHelperDemo {public static void main(String[] args) {// 示例…

【spring】@Lazy注解学习

Lazy介绍 Lazy 注解是一个配置注解&#xff0c;用于指示 Spring 容器在创建 bean 时采用延迟初始化的策略。这意味着&#xff0c;除非 bean 被实际使用&#xff0c;否则不会被创建和初始化。 在 Spring 框架中&#xff0c;默认情况下&#xff0c;所有的单例 bean 在容器启动时…

运用YOLOv5实时监测并预警行人社交距离违规情况

YOLO&#xff08;You Only Look Once&#xff09;作为一种先进的实时物体检测算法&#xff0c;在全球范围内因其高效的实时性能和较高的检测精度受到广泛关注。近年来&#xff0c;随着新冠疫情对社交距离管控的重要性日益凸显&#xff0c;研究人员开始将YOLO算法应用于社交距离…

关于Count,FPKM,TPM,RPKM等表达量的计算及转换 | 干货

原文链接:关于Count,FPKM,TPM,RPKM等表达量的计算及转换 | 干货 写在前面 今天使用count值转化TPM,或是使用FPKM转换成TPM。这样的教程,我们在前面已经出国一起相对比较详细的教程了,一文了解Count、FPKM、RPKM、TPM | 相互间的转化,在这个教程中,我们也归纳了各个数…

【力扣hot100】128.最长连续序列

给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1&#xff1a; 输入&#xff1a;nums [100,4,200,1,3,2] 输出&#xff1a;4 解…

DMA的定义和作用

在计算机系统中&#xff0c;DMA&#xff08;Direct Memory Access&#xff0c;直接内存访问&#xff09;是一种用于提高数据传输效率的重要技术。本文将介绍DMA的定义、原理和作用&#xff0c;以及它在计算机系统中的重要性。 以下是我整理的关于嵌入式开发的一些入门级资料&a…

app开发中HBuilderX运行模拟器 配置模拟器手册

1.首先打开HBuilder 然后点击,左上角运行 2.点击运行到手机或模拟器内的ADB路径设置(A) 3. adb配置你模拟器的 adb.exe路径端口号配置你模拟器的端口号 我这里使用的逍遥模拟器所以 | 21503 端口 | 手机模拟器名称21503 端口逍遥模拟器21503夜神模拟器62001网易mumu模拟器7…

点餐小程序php毕设项目

主要技术框架&#xff1a; 主要功能模块&#xff1a; 商品管理 订单管理 用户管理 优惠券管理 商品分类管理 评论管理 轮播图管理 截图 获取源码 https://blog.lusz.top/article?article_id-2

Talk|Mila研究所蒙特利尔大学刘圳:三维表征和三维网格的重建与生成

本期为TechBeat人工智能社区第580期线上Talk。 北京时间3月21日(周四)20:00&#xff0c;Mila研究所&蒙特利尔大学博士生—刘圳的Talk已经准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “三维表征和三维网格的重建与生成”&#xff0c;向大家系统地介…

【软考】生成树

目录 1. 概念2. 图解3. 例题3.1 例题1 1. 概念 1.对于有n个顶点的连通图&#xff0c;至少有n-1条边&#xff0c;而生成树中恰好有n-1条边2.连通图的生成树是该图的极小连通子图3.若在图的生成树中任意加一条边&#xff0c;则必然形成回路4.图的生成树不是唯一的5.从不同的顶点…

GPU云服务器的优势和应用

GPU即图形处理器&#xff0c;是一种高性能计算加速器&#xff0c;主要用于处理复杂的图像、视频等。GPU云服务器&#xff0c;指的是在云计算环境下&#xff0c;通过云平台提供GPU计算能力的虚拟服务器。随着科技的迅猛发展&#xff0c;科技领域对其的讨论和应用也日益热烈、广泛…

ios symbolicatecrash 符号化crash

一、准备 1.1 .crash 文件获取 设备连接电脑打开XCode, 依次 XCode -> Windows -> Device and Simulator -> View Device Logs找到 (对应app名+时间点) -> 右键 Export Log1.2 .dSYM 和 .app 文件获取 .dSYM是十六进制函数地址映射信息的中转文件,调试的symbols…

【Leetcode】top 100 链表

基础知识补充 单向链表结构&#xff1a;item存储数据 next指向下一结点地址 head保存首地址 class Node(object): # 创建结点def __init__(self, item): self.item item # item存放数据元素self.next None # next是下一个…

人才测评三要素:需求、量表和在线工具

人才测评在企业招聘&#xff0c;和企业内部测评&#xff0c;团队优化&#xff0c;团队建设&#xff0c;晋升考评中存在大量的普遍的应用&#xff0c;对于公司财力足够的情况&#xff0c;完全可以把人才测评的活外包给专业的测评机构&#xff0c;而对于大量的中小企业来说&#…

MyBatis3源码深度解析(十九)MyBatis日志实现

文章目录 前言第七章 MyBatis日志实现7.1 Java日志体系7.1.1 常用日志框架7.1.2 Java日志发展史7.1.3 日志接口与日志实现的绑定 7.2 MyBatis日志实现7.2.1 Log接口7.2.2 LogFactory工厂7.2.3 MyBatis日志自动查找7.2.4 MyBatis日志类型配置 7.3 小结 前言 日志是Java应用中必…

VSCode创建用户代码片段-案例demo

示例 - 在线生成代码片段 Vue3代码片段 {"vue3": {scope": "javascript,typescript,html,vue","prefix": "vue3","body": ["<template>","$1","</template>",""…

基于 SemiDrive E3640 Gateway SSDK3.0 Sent 测试

一、 前言 SENT 全称&#xff1a;Single Edge Nibble Transmission&#xff0c;中文名称为&#xff1a;单边半字传输协议&#xff0c;是 SAE 推出的一种点对点的、单向传输的方案&#xff0c;被用于车载传感器和电子控制单元&#xff08;ECU&#xff09;之间的数据传输。SENT(S…

SSC9211_USB-CAM解决方案

一、方案描述 SSC9211是一种用于USB-CAM应用程序跟场景的高度集成的SOC产品。平台本身基于ARM层-A7双核&#xff0c;内置16位&#xff0c;64M的DDR2&#xff0c;集成了图像传感器接口、高级ISP、高性能JPEG编码器和其他丰富的外设接口。支持单&#xff0c;双 MIPI sensor方案&…

阻塞赋值与非阻塞赋值

1.原理 一个寄存器可以实现延迟一拍的效果。 可以看出输出out和中间寄存器in_reg确实 和输入in相差一拍&#xff0c;也就是一个时钟周期。 而out和in_reg没有延迟一拍是因为使用的是阻塞赋值。右边发生变化&#xff0c;左边立刻变化。 使用非阻塞赋值。 可以看到中间变量In_r…

安卓使用MQTT实现阿里云物联网云台订阅和发布主题(3)

一、订阅主题代码讲解 private final String mqtt_sub_topic "/sys/k0wih08FdYq/LHAPP/thing/service/property/set";//订阅话题//mqtt客户端订阅主题//QoS0时&#xff0c;报文最多发送一次&#xff0c;有可能丢失//QoS1时&#xff0c;报文至少发送一次&#xff0c…