剖析 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()获取函数对象

相关推荐
weixin_985432112 小时前
Spring Boot 中的 @ConditionalOnBean 注解详解
java·spring boot·后端
猎人everest3 小时前
快速搭建运行Django第一个应用—投票
后端·python·django
啾啾Fun5 小时前
精粹汇总:大厂编程规范(持续更新)
后端·规范
yt948325 小时前
lua读取请求体
后端·python·flask
IT_10245 小时前
springboot从零入门之接口测试!
java·开发语言·spring boot·后端·spring·lua
汪子熙6 小时前
在 Word 里编写 Visual Basic 调用 DeepSeek API
后端·算法·架构
寻月隐君6 小时前
手把手教你用 Solana Token-2022 创建支持元数据的区块链代币
后端·web3·github
代码丰7 小时前
使用Spring Cloud Stream 模拟生产者消费者group destination的介绍(整合rabbitMQ)
java·分布式·后端·rabbitmq
烛阴7 小时前
Cheerio DOM操作深度指南:轻松玩转HTML元素操作
前端·javascript·后端
Hello.Reader8 小时前
在多云环境透析连接ngx_stream_proxy_protocol_vendor_module
后端·python·flask