is not a function_libcxx 的 std::function 源码分析

链接:functional。其中 std::function 的主体内容在 2100 多行。

先来看 function 的头部。

template

其中的 libcpp template vis 宏是用来 controlling symbol visibility 的,我们先不用管。我们先看两个继承,这两个 maybe 类是 traits 类。模板元编程常见的技巧。如果 Rp 和 ArgTypes 满足一元或者二元函数的模板模式(偏特化)的话,那么就继承相应的 unary function 或者 binary function。如果不满足的话就继承空类。其中 unary function 定义是这样。

template 

binary 类似。其实就是 typedef 了函数相关类型的空类。

接下来是 std::function 中的数据成员。我们可以看到条件编译。

#ifndef _LIBCPP_ABI_OPTIMIZED_FUNCTION

其中这个 libcpp abi optimized function 是后加的一个优化选项。可以看到原来用的是 value func 而优化之后用 policy func。具体的 patch 可以见PATCH D55045。两者究竟有什么区别呢?我们瞧瞧。

我们去跟踪 value func。可以看到 value func 的数据成员是这样的。

template 

我们看到 value func 里面有一个 buf。这个 buf 是 3 个指针长度大小。我们这里假定在 64 位机器吧。那么这个 buf 就是 24 字节。然后还有一个 func 指针指向了一个 base 对象。所以我们得到了一个重要的结论:一个 value func 就是 4 个指针长度,32字节。base 对象是干嘛的?我们跟踪一下。我们可以在代码里看到这样一句话。

// __base provides an abstract interface for copyable functors.

然后是 base 类的定义。就是一个抽象的接口,里面一大堆 = 0 的函数,说是指向一个可拷贝的函子。那这个 buf 又是干嘛的呢?我们继续挖 value func。

这是在 value func 的构造函数代码段节选。

if 

可以看到,我们的构造函数根据 sizeof Fun 有了差别。如果 buf 能装得下 Fun(并且拷贝不会抛异常)那么我们的 func 指针直接去指向 buf。并且在 buf 上构造(replacement new)我们的 Fun。如果装不下(或者拷贝抛异常)那么就只能 allocate 出来内存来存放这个 Fun 了。至于这里为什么用 unique ptr 这么迂回的方式,我也不是很懂,但是我猜测和异常有关。所以,我们又得到了一个重要的结论,如果 buf 上可以分配得下 Fun(并且拷贝不抛异常),那么直接在 Buf 上分配(栈)。否则会去 allocate 内存。

关于 value func 就说到这,value func 里面其他的东西都比较正常。其中 value func 的 swap 函数实现的也比较崎岖,感兴趣可以看看。

接下来是 policy func 了。首先我们先看 policy storage。

union 

可以看到这时候用一个 union 节省了内存。并且 small 的 size 变成了 2 个指针长度即 16 个字节。一般的函数指针是不会超过这个数的,但是其他的函子可以很膨胀。这里留一个思考题:为什么一般的函数指针不会超过 16 个字节,而不是 8 个字节?提示:成员函数指针。

这是一个配套 policy storage 使用的 traits 类。

template 

很显然,如果 Fun 满足条件那么 use small storage 里的 value 是 true,否则是 false。很简单的模板元编程。注意这里的条件没有 allocator 了。因为在 C++17 之后,std::function 就不用 allocator 了。

在看 policy func 之前,我们还得看一个类,invoker。

// __policy_invoker calls an instance of __alloc_func held in __policy_storage.

其中 fast forward 先不用管,是一个传参策略的 traits。我们看到 policy invoker 里面有一个 Call 函数指针。这个函数指针指向的函数接受 policy storage 和函子的参数,然后返回函子的返回值。为什么需要这么一个类呢?我们往下看。

// Creates an invoker that calls the given instance of __func.

这里返回了一个 policy invoker 对象,并且用 call impl 初始化了 Call 函数指针。再瞧瞧 call impl。

template 

可以看到这个 call impl 函数先判定 Fun 存储在哪里,然后就去调用它。

所以,为什么需要 invoker 呢?因为我们这里用的是(内存和指向其他内存的指针)的一个 union。所以取内存方式会不同,中间会差一层取地址的抽象(见源码的 small 和 large 的取法,small 要多一层取地址)。

而 value func 为什么可以统一取内存的方式呢?因为 value func 的指向内存的指针 func 和 buf 是分开的,即使分配到了 buf 上,value func 的 func 指针依然会指向这个 buf。所以无论如何,只要通过这个 func 指针来获取内存,一定就是没错的。没有疑问。

好了,我们可以看 policy func 了。

// __policy_func uses a __policy and __policy_invoker to create a type-erased,

可以看到首先 policy func 里面有 buf,16 个字节。然后是 invoker,8 个字节。最后是 policy 指针,8 个字节。所以加起来还是 32 个字节。其中 policy 类似于 base,也是提供如何进行基本操作的类的指针。

我们再来看看 policy func 的构造函数。

template 

和 value func 差不多。不再赘述了。注意这里的分配内存之后并没有赋值给一个指针,而仅仅是在 buf 上分配内存就完了。获取函子并调用的操作是由 invoker 做的。

我觉得 std::function 的重点就是数据成员这部分。至于相关的成员函数,我觉得都比较正常,这里就不在多说什么了。

那,就这样。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值