STL中 function 源码解析

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 简单模仿其功能

struct Ptr
{
    int* pad; // add pad to show difference between 'this' and 'data'
    int* data;
    Ptr(int* arg) : pad(nullptr), data(arg)
    {
        std::cout << "Ctor this = " << this << '\n';
    }
    Ptr(int* arg, int *arg1) : pad(arg1), data(arg)
    {
        std::cout << "Ctor this = " << this << '\n';
    }
    Ptr(Ptr&& rp) {
        pad = rp.pad;
        data = rp.data;
        rp.pad = nullptr;
        rp.data = nullptr;
        std::cout << "Ctor this = " << this << '\n';
    }
    void myfunc() {
        std::cout << "myfunc " << '\n';
    }
    void operator()() {
        std::cout << "data = " << *data << std::endl;
    }
    // ~Ptr() { delete data; }
};
 

char arr[16];

void* myaccess() {
    std::cout << "xxx" << std::endl;
    return &arr[0];
}

template<typename _Tp>
_Tp& _M_access() {
    auto tmp = static_cast<_Tp*>(myaccess());
    // _Tp tmp1 = (*tmp);
    return *static_cast<_Tp*>(myaccess());
}

int main()
{
    int x = 42;

    int y = 16;
    
    std::cout << &y << '\n';
    
    Ptr p(&x, &y);
    ::new (myaccess()) Ptr(std::move(p));

    Ptr* op1 = _M_access<Ptr*>();                    // 获取的是 pad 存储的内容,即 y 的地址;

    const Ptr& __f = _M_access<Ptr>();

    Ptr* op2 = const_cast<Ptr*>(std::__addressof(__f));

    std::cout << op1 << std::endl;                   // 

    std::cout << op2 << std::endl;

    // --------------------------------------------

    // Ptr p(&x, &y);

    // ::new (myaccess()) Ptr*(std::move(&p));

    // Ptr* op1 = _M_access<Ptr*>();

    // const Ptr& __f = _M_access<Ptr>();

    // Ptr* op2 = const_cast<Ptr*>(std::__addressof(__f));

    // std::cout << &p << std::endl;              // 0x7ffebd392100

    // std::cout << op1 << std::endl;             // x 变量的地址

    // std::cout << op2 << std::endl;  

    // std::cout << "orin " << myaccess() << std::endl;  


    // std::cout << (void *)myaccess << std::endl;  

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

git clone 后如何 checkout 到 remote branch

what/why 通常情况使用git clone github_repository_address下载下来的仓库使用git branch查看当前所有分支时只能看到master分支&#xff0c;但是想要切换到其他分支进行工作怎么办❓ 其实使用git clone下载的repository没那么简单&#x1f625;&#xff0c;clone得到的是仓库…

23种设计模式之创建型模式 - 单例模式

文章目录 一、单例模式1.1单例模式定义1.2 单例模式的特点 二、实现单例模式的方式2.1 饿汉式2.2 懒汉式2.3 双重检查锁&#xff1a;2.4 静态内部类2.5 枚举实现&#xff08;防止反射攻击&#xff09;&#xff1a; 一、单例模式 1.1单例模式定义 单例模式确保系统中某个类只有…

docker学习笔记 四-----docker基本使用方法

基础命令奉上&#xff1a; 1、docker命令查询方法 docker --help 获取docker命令帮助 docker search --help 查询docker 子命令search的帮助 2、查询镜像 查询镜像 docker search 192.168.206.100:5000/mysql 查询指定服务器指定镜像 docker search mysql …

Redis入门到实战-第二十弹

Redis实战热身Time series篇 完整命令参考官网 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://redis.io/Redis概述 Redis是一个开源的&#xff08;采用BSD许可证&#xff09;&#xff0c;用作数据库、缓存、消息代…

Redis入门到实战-第十六弹

Redis实战热身Cuckoo filter篇 完整命令参考官网 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://redis.io/Redis概述 Redis是一个开源的&#xff08;采用BSD许可证&#xff09;&#xff0c;用作数据库、缓存、消息…

Transformer的前世今生 day10(Transformer编码器

前情提要 ResNet&#xff08;残差网络&#xff09; 由于我们加更多层&#xff0c;更复杂的模型并不总会改进精度&#xff0c;可能会让模型与真实值越来越远&#xff0c;如下&#xff1a; 我们想要实现&#xff0c;加上一个层把并不会让模型变复杂&#xff0c;即没有它也没关系…

【启发式算法】同核分子优化算法 Homonuclear Molecules Optimization HMO算法【Matlab代码#70】

文章目录 【获取资源请见文章第4节&#xff1a;资源获取】1. 算法简介2. 部分代码展示3. 仿真结果展示4. 资源获取 【获取资源请见文章第4节&#xff1a;资源获取】 1. 算法简介 同核分子优化算法&#xff08;Homonuclear Molecules Optimization&#xff0c;HMO&#xff09;是…

数据结构面试常见问题之串的模式匹配(KMP算法)系列-大师改进

&#x1f600;前言 KMP算法是一种改进的字符串匹配算法&#xff0c;由D.E.Knuth&#xff0c;J.H.Morris和V.R.Pratt提出&#xff0c;因此人们称它为克努特—莫里斯—普拉特操作&#xff08;简称KMP算法&#xff09; KMP算法的优势: 提高了匹配效率&#xff0c;时间复杂度为O(m…

力扣面试150 移除元素 双指针

Problem: 27. 移除元素 思路 &#x1f468;‍&#x1f3eb; 三叶题解 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( 1 ) O(1) O(1) Code class Solution {public int removeElement(int[] nums, int val) {int j nums.length - 1;for (int i 0; i < j;…

Netty服务端基本启动流程源码刨析

前言: 希望看这篇文章之前对Java Nio编程比较熟悉&#xff0c;并有用过Netty开发简单代码 服务端代码 先大致说一下NioEventLoopGroup组件的作用&#xff0c;可以把它看是作内部维护了一个NioEventLoop数组的对象&#xff0c;它的构造方法的参数用来指定维护数组的大小。NioEve…

快速上手Spring Cloud 十:Spring Cloud与微前端

快速上手Spring Cloud 一&#xff1a;Spring Cloud 简介 快速上手Spring Cloud 二&#xff1a;核心组件解析 快速上手Spring Cloud 三&#xff1a;API网关深入探索与实战应用 快速上手Spring Cloud 四&#xff1a;微服务治理与安全 快速上手Spring Cloud 五&#xff1a;Spring …

强化基础-Java-泛型

什么是泛型&#xff1f; 泛型其实就参数化类型&#xff0c;也就是说这个类型类似一个变量是可变的。 为什么会有泛型&#xff1f; 在没有泛型之前&#xff0c;java中是通过Object来实现泛型的功能。但是这样做有下面两个缺陷&#xff1a; 1 获取值的时候必须进行强转 2 没有…

Learn OpenGL 26 视差贴图

什么是视差贴图 视差贴图(Parallax Mapping)技术和法线贴图差不多&#xff0c;但它有着不同的原则。和法线贴图一样视差贴图能够极大提升表面细节&#xff0c;使之具有深度感。它也是利用了视错觉&#xff0c;然而对深度有着更好的表达&#xff0c;与法线贴图一起用能够产生难…

商标跨类异议与跨类保护!

有个朋友对普推知产老杨说收到某邮件&#xff0c;名下商标让某公司抢注了现在公告期&#xff0c;让赶紧提出来异议去处理下&#xff0c;怎么会有这样的事&#xff0c;相同的名称基本上在同类别相关产品是无法公告和获得初审的。 经详细检索分析后&#xff0c;发现不是这样一回…

【Linux】理解父子进程(系统调用创建进程,fork函数,写时拷贝)

目录 fork函数 返回值 内存分配 父子进程是操作系统一个重要的概念&#xff0c;特别是在多任务处理和并发编程中&#xff0c;在Linux中&#xff0c;每个进程都有一个唯一的进程ID&#xff0c;并且每个进程都有可能创建其他进程。当一个进程创建了一个新的进程时&#xff0c;…

【Linux】进程>环境变量地址空间进程调度

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;Linux_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.环境变量 1.1 基本概念 1.2 常见环境变量 1.3 查看环境变量方法 1.4 和环境变量相关的命令 1.5 环境变量的组织方式 1.6 通…

最“原始”的收音机长啥样?

同学们大家好&#xff0c;今天我们继续学习杨欣的《电子设计从零开始》&#xff0c;这本书从基本原理出发&#xff0c;知识点遍及无线电通讯、仪器设计、三极管电路、集成电路、传感器、数字电路基础、单片机及应用实例&#xff0c;可以说是全面系统地介绍了电子设计所需的知识…

vs code

vs code 下载安装 https://code.visualstudio.com/https://code.visualstudio.com/ 下载完后&#xff0c;下一步下一步就安装完了&#xff0c;安装好后可以下载各种好用的插件

【前端面试3+1】03深拷贝浅拷贝、let和var、css盒模型、【有效括号】

一、深拷贝浅拷贝 深拷贝和浅拷贝都是用于复制对象或数组的概念&#xff0c;但它们之间有着重要的区别&#xff1a; 1. 浅拷贝&#xff1a; 浅拷贝是指在拷贝对象或数组时&#xff0c;只会复制一层对象的属性或元素&#xff0c;而不会递归地复制嵌套的对象或数组。因此&#xf…