剖析 std::function源码

代码位于 bits/std_function.h中

1. UML类关系图

这里用到了两个union(联合体). _Any_data有个_M_pod_data数组,用来存储我们构造一个function时,传入的参数.比如下列代码:

cpp 复制代码
void foo() {}

int main()
{
    function<void()> f1(foo);
}

_M_pod_data就是用来存储参数foo函数指针的.这个没有动态分配内存,所以是存放在栈上的。

_Nocopy_types分别定义了四个指针,联合体的大小是根据成员变量最大为准.这里定义了一个void (_Undefined_class::*_M_member_pointer)(),这个占用大小一般为2个指针的大小,分别用来指向类对象成员函数 .

2. _Function_base基类

_Function_base定义了两个成员变量
第一个成员变量不用说了,就是用来存储function构造时候的参数.

第二个变量是一个函数指针,用来指向一个函数.稍后到_Function_handler类会讲到

3. _Base_manager

这个类定义为_Function_base类中.这个类提供的静态函数主要用于:

  • 保存函数对象\指针
  • 析构函数对象\指针
  • 提供一个_M_manager函数用于std::function的target_type()target()函数

还定义了一个静态成员变量__stored_locally
这里用到了__is_location_invariant来判断_Functor是否是位置不变的,确定是否可以安全存储在局部变量中(即是否可以在栈上分配).
什么叫位置不变的: 就是在不同的位置具有相同的表示,这种特性被称为位置不变 当_Functor满足以下条件时,value被设置为true:

  1. 如果_Functor是可复制的类型, 且满足以下条件之一:
  • 是一个内置数据类型
  • 是一个数组类型,且元素类型是位置不变的
  • 是一个结构体类型,且每个非静态数据成员都是位置不变的
  1. _Functor是一个标准库类型,并且容器中的元素类型是位置不变的
  2. _Functor是一个标准库智能指针类型,并且指针所指向的类型是位置不变的
  3. 函数类型也是可复制的,所以value被设置为true

下图这俩结合使用,作用是不同版本的成员函数._Base_manager提供了两个版本的_M_create_M_destroy函数.这两个版本分别是操作_Any_data中内存和堆上的内存.

4. _Function_handler

通过名字可以得知,这个类主要就是用来执行函数的.

这个类定义了一个静态成员函数_M_invoke,底层调用std::__invoke_r用来执行函数

5. function

在function头部定义了一个模板类和三个模板别名

第一个_Decay_t作用是如果模板参数_Funcfunction不是同一个类型,那么_Decay_t则是_Func退化后的类型.

第二个类模板_Callable.根绝给定的函数类型_Func,将其退化后为_DFunc类型;然后根据给定的参数类型_ArgTypes...执行调用操作,得到调用结果类型_Res2;最后,判断_Res2是否与给定的类型_Res相符合,如果相符合,则_Callable继承自std::true_type,表示可调用;否则继承自std::false_type,表示不可调用.

第三个模板参数是一个条件,如果_Cond::value为true,则_Requires等价于_Tp类型

第四个用来执行函数,将模板参数_Functor退化

5.1 构造、析构函数

5.1.1 构造函数

注意: 这个构造函数的参数是右值引用

模板参数typename _Constraints = _Requires<_Callable<_Functor>>,这一步检测模板参数_Functor必须是可调用的.

逐行分析函数体代码:

首先定义一个_Handler的别名_My_handler,操作_My_handler也就是操作_Function_handler

下面判断构造函数参数__f是否是一个空函数.这里调用_Function_handler_M_not_empty_function函数,这个函数一共有四个版本:

如果_M_not_empty_function返回为true,那么则进入if语句块里,执行初始化.

进行初始化调用_Function_handler_M_init_functor函数,第一个参数_M_functor是从基类_Function_base继承而来的,类型是_Any_data.

下面是_Function_handler_M_init_functor函数,初始化一个functor 底层调用_M_create函数,该函数接收三个参数.

  1. 第一个参数存放Functor的内存位置
  2. 第二个参数是函数对象
  3. 第三个是一个特定结构体

这里第三个参数调用_Local_storage(),这个判断_Functor是否是位置不变的。对于函数类型是可复制的,位置不变的.所以调用_M_createtrue_type版本 则在_Any_data上构造对象,也就是上构造一个函数对象.

初始化functor之后,回到function构造函数里.

接下来设置两个函数指针 ,分别指向_Function_handler_M_invoke函数; 和_Function_handler_M_manager函数.
到此为止,整个构造过程结束

5.1.2 析构函数

function并没有显式的定义析构函数,而基类_Function_base定义了析构函数.所以在析构function的时候,会调用_Function_base显式定义的析构函数

这里判断_M_manager函数指针是否被设置了,如果不为空,则表示已经初始化了functor.这里调用_Function_base的静态成员函数_M_manager.注意,第三个参数传入为 __destroy_functor,表示销毁functor
底层调用_M_destroy函数来销毁functor.因为_M_destroy也有两个版本,所以第二个参数传入_Local_storage()来匹配合适的版本.
两个版本一个销毁存储在_Any_data上的,一个销毁在堆上的.

5.2 拷贝、移动构造函数

5.2.1 拷贝构造

首先判断参数__x是否是一个已经初始化functor的了. static_cast<bool>(__x))这个代码则调用operator bool()函数
底层调用_M_empty

如果参数__x不为empty的话,则进入到if语句块,进行拷贝.
调用_M_manager函数,第三个参数为__clone_functor表示拷贝functor
最后复制参数__x的函数指针_M_invoker_M_manager

5.2.2 移动构造

移动构造相对简单,直接设置变量就可以了

5.3 operator=赋值运算符

拷贝运算符

移动运算符

赋值为nullptr运算符 如果先前已经初始化了functor,则调用_M_manager,传入参数__destroy_functor销毁创建的functor,随后将函数指针都设置为nullptr

5.4 operator()(_ArgTypes... __args)函数调用运算符

调用之前首先检查是否已经初始化了functor,如果没有则抛出异常终止程序.否则调用一个函数指针_M_invoker.定义为function类的末尾.
在构造函数的时候已经设置将这个函数指针指向_Function_handler类的_M_invoke函数

上面这里将functor和函数所需参数都传入.
_Function_handler的静态函数_M_invoke,最终调用标准库__invoke_r函数.最终执行函数调用.

5.5 target_type()获取函数对象类型

定义一个临时变量__typeinfo_result,用来存储当前函数对象的类型. 然后调用_M_manager获取函数对象类型.
最后获取到临时变量__typeinfo_result里存储的函数对象类型

5.6 target()获取函数对象

相关推荐
卷毛的技术笔记1 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
会编程的土豆1 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
喵个咪2 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
basketball6162 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
qq_2518364572 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
zhangxingchao2 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
IT_陈寒4 小时前
Vite打包时遇到的坑,原来问题出在这里
前端·人工智能·后端
ayqy贾杰5 小时前
基层管理的三板斧,在AI时代行不通了
前端·后端·团队管理
Apifox5 小时前
Apifox 5 月更新|Postman 导入优化、Runner 支持非 root 运行、请求代码自动带鉴权
前端·后端·安全
JohnYan5 小时前
工作笔记 - PG分组极值
数据库·后端·postgresql