STL源码阅读小记(三)——Function

前言

这几天简单的看了下list,deque发现基本实现和stl源码刨析中基本类似,由于stl是个大块头,就打算选择性的看把,一些像queue,stack这样的适配器也粗略瞄一眼,就不细看了。刚好最近使用funtion比较频繁,所以这次来看下function的实现。

STL版本

本文所使用的stl版本为libc++ 13.0,属于LLVM项目。

LLVM项目Github

如果遇到不熟悉的宏定义可以参考文档Symbol Visibility Macros

function

先看看function的定义,由于function使用可变参数模板,但是在声明中不允许出现可变参数模板,所以就直接跳过声明了。

template<class _Fp> class _LIBCPP_DEPRECATED_CXX03_FUNCTION _LIBCPP_TEMPLATE_VIS function; //声明
	
template<class _Rp, class ..._ArgTypes>	//定义
class _LIBCPP_TEMPLATE_VIS function<_Rp(_ArgTypes...)>
#if _LIBCPP_STD_VER <= 17 || !defined(_LIBCPP_ABI_NO_BINDER_BASES)
    : public __function::__maybe_derive_from_unary_function<_Rp(_ArgTypes...)>,
      public __function::__maybe_derive_from_binary_function<_Rp(_ArgTypes...)>
#endif

首先解释一下模板参数,_Rp为函数返回类型,_ArgTypes是参数类型,是一个可变参。

至于继承的两个对象是历史原因,需要兼容旧的代码,大致是当函数参数为一个的时候就会继承__maybe_derive_from_unary_function的特化,两个会继承__maybe_derive_from_binary_function,c++17之后删除,就不讨论了。

接下来是function的核心

#ifndef _LIBCPP_ABI_OPTIMIZED_FUNCTION
    typedef __function::__value_func<_Rp(_ArgTypes...)> __func;
#else
    typedef __function::__policy_func<_Rp(_ArgTypes...)> __func;
#endif
	__func __f_;

__f_是function实际存储的地方,正常是__function::__value_func,但是后面有一位大佬进行了优化,做出了__function::__policy_func,对小于2 * sizeof(void*)的非平凡小对象进行了优化,不过里面具体是什么,待看完function后揭晓。

//__value_func和__policy_func性能对比
Benchmark                              Time             CPU      Time Old  =
    Time New       CPU Old       CPU New
---------------------------------------------------------------------------=
----------------------------------------

BM_MixedFunctorTypes                -0.3794         -0.3794         19661  =
       12202         19660         12202

如果刚兴趣也可以看看提交,[PATCH] D55045: Add a version of std::function that includes a few optimizations.

接着看function的构造函数吧

 template <class _Fp, bool = _And<
        _IsNotSame<__uncvref_t<_Fp>, function>,
        __invokable<_Fp, _ArgTypes...>
    >::value>
    struct __callable;
    template <class _Fp>
        struct __callable<_Fp, true>
        {
            static const bool value = is_void<_Rp>::value ||
                __is_core_convertible<typename __invoke_of<_Fp, _ArgTypes...>::type,
                                      _Rp>::value;
        };
    template <class _Fp>
        struct __callable<_Fp, false>
        {
            static const bool value = false;
        };

  template <class _Fp>
  using _EnableIfLValueCallable = typename enable_if<__callable<_Fp&>::value>::type;

public:
			template<class _Fp, class = _EnableIfLValueCallable<_Fp>>
    function(_Fp);

首先调了一个最典型的构造函数的声明,可以发现function的构造是一个模板函数,第一模板参数为函数,比如function<void()>,_Fp就是void(),第二个模板参数是一个检查,检查第一个参数是否为一个可调用对象。

利用__callable的第二个模板参数进行特化,_IsNotSame利用模板特化判断两个模板参数是否一样。可以看下原代码。

template <class _Tp, class _Up> struct _LIBCPP_TEMPLATE_VIS is_same           : public false_type {};
template <class _Tp>            struct _LIBCPP_TEMPLATE_VIS is_same<_Tp, _Tp> : public true_type {};

template <class _Tp>
struct __uncvref  {
    typedef _LIBCPP_NODEBUG_TYPE typename remove_cv<typename remove_reference<_Tp>::type>::type type;
};

两个模板参数一样会特化为第二个,不一样会特化为第一个,、__uncvref_t则是利用类型萃取移除引用。所以这里is_same是判断_Fp是否是一个function类型,并且利用__invokable<_Fp, _ArgTypes…>判断是否是可调用对象。

当_Fp不是function类型,并且是可调用对象,__callable则特化为struct __callable<_Fp, true>。

接着我们看构造函数的定义。

template <class _Rp, class... _ArgTypes>
template <class _Fp, class>
function<_Rp(_ArgTypes...)>::function(_Fp __f) : __f_(_VSTD::move(__f)) {}

仅仅是委托给__f_处理。顺带一提()调用与析构都差不多如此。

template<class _Rp, class ..._ArgTypes>
_Rp
function<_Rp(_ArgTypes...)>::operator()(_ArgTypes... __arg) const
{
    return __f_(_VSTD::forward<_ArgTypes>(__arg)...);
}
	
template<class _Rp, class ..._ArgTypes>
function<_Rp(_ArgTypes...)>::~function() {}

__value_func

template <class _Fp> class __value_func;

template <class _Rp, class... _ArgTypes> class __value_func<_Rp(_ArgTypes...)>
{
    typename aligned_storage<3 * sizeof(void*)>::type __buf_;

    typedef __base<_Rp(_ArgTypes...)> __func;
    __func* __f_;

首先_value_func成员有两个,一个为pod类型的_buf,另一个为_base指针的_f。首先来看一下__buf_是什么把。

__buf_的类型是aligned_storage<3 * sizeof(void*)>::type,aligned_storage这个类的作用是提供一个模板参数大小的POD类型,详细的看这里aligned_storage

__base<_Rp(_ArgTypes…)>则为一个虚基类,实际存储的的是__func<_Fp, _Alloc, _Rp(_ArgTypes…)>,这个我们等等在说,先看下构造函数和析构函数吧。

 template <class _Fp, class _Alloc>
    _LIBCPP_INLINE_VISIBILITY __value_func(_Fp&& __f, const _Alloc& __a)
        : __f_(nullptr)
    {
        typedef allocator_traits<_Alloc> __alloc_traits;
        typedef __function::__func<_Fp, _Alloc, _Rp(_ArgTypes...)> _Fun;
        typedef typename __rebind_alloc_helper<__alloc_traits, _Fun>::type
            _FunAlloc;

        if (__function::__not_null(__f))
        {
            _FunAlloc __af(__a);
            if (sizeof(_Fun) <= sizeof(__buf_) &&
                is_nothrow_copy_constructible<_Fp>::value &&
                is_nothrow_copy_constructible<_FunAlloc>::value)
            {
                __f_ =
                    ::new ((void*)&__buf_) _Fun(_VSTD::move(__f), _Alloc(__af));
            }
            else
            {
                typedef __allocator_destructor<_FunAlloc> _Dp;
                unique_ptr<__func, _Dp> __hold(__af.allocate(1), _Dp(__af, 1));
                ::new ((void*)__hold.get()) _Fun(_VSTD::move(__f), _Alloc(__a));
                __f_ = __hold.release();
            }
        }
    }

    _LIBCPP_INLINE_VISIBILITY
    ~__value_func()
    {
        if ((void*)__f_ == &__buf_)
            __f_->destroy();
        else if (__f_)
            __f_->destroy_deallocate();
    }

可以发现当传入的仿函数小于3sizeof(void),也就是24字节时,会在自身__buf_存储,否则用分配器进行分配空间。并且使用分配器时候还利用unique_ptr进行构造,用unique_ptr的好处在于方便,比如在构造过程中抛出异常了,则unique_ptr会自动调用析构函数。

接下来是重载()调用,其实也是调用内部__f_的()重载.

   _LIBCPP_INLINE_VISIBILITY
    _Rp operator()(_ArgTypes&&... __args) const
    {
        if (__f_ == nullptr)
            __throw_bad_function_call();
        return (*__f_)(_VSTD::forward<_ArgTypes>(__args)...);
    }

__func和__base

__func是__base的基类,而__base是一个虚基类,规定了子类必需要实现的虚函数。

template<class _Rp, class ..._ArgTypes>
class __base<_Rp(_ArgTypes...)>
{
    __base(const __base&);
    __base& operator=(const __base&);
public:
    _LIBCPP_INLINE_VISIBILITY __base() {}
    _LIBCPP_INLINE_VISIBILITY virtual ~__base() {}
    virtual __base* __clone() const = 0;
    virtual void __clone(__base*) const = 0;
    virtual void destroy() _NOEXCEPT = 0;
    virtual void destroy_deallocate() _NOEXCEPT = 0;
    virtual _Rp operator()(_ArgTypes&& ...) = 0;
#ifndef _LIBCPP_NO_RTTI
    virtual const void* target(const type_info&) const _NOEXCEPT = 0;
    virtual const std::type_info& target_type() const _NOEXCEPT = 0;
#endif // _LIBCPP_NO_RTTI
};

而__func_内部又存储着一个__alloc_func

template<class _FD, class _Alloc, class _FB> class __func;

template<class _Fp, class _Alloc, class _Rp, class ..._ArgTypes>
class __func<_Fp, _Alloc, _Rp(_ArgTypes...)>
    : public  __base<_Rp(_ArgTypes...)>
{
    __alloc_func<_Fp, _Alloc, _Rp(_ArgTypes...)> __f_;
	///函数略,基本都是对__f_的操作
}

__alloc_func

到这着一步就差不多要将function扒到底了,我们看下着__alloc_func里到底是啥。

template <class _Fp, class _Ap, class _Rp, class... _ArgTypes>
class __alloc_func<_Fp, _Ap, _Rp(_ArgTypes...)>
{
    __compressed_pair<_Fp, _Ap> __f_;

  public:
    typedef _LIBCPP_NODEBUG_TYPE _Fp _Target;
    typedef _LIBCPP_NODEBUG_TYPE _Ap _Alloc;
	//函数略
	}

可以发现__alloc_func有__compressed_pair类,这个算一个挺好的优化,__compressed_pair可以理解为一个pair,但是在某一个或都为空的时候更节约空间,因为大家可以sizeof一个空类试一下,可以发现一个空类的大小为1。这里就是利用继承省略空类的空间。我们来看下__compressed_pair。

template <class _T1, class _T2>
class __compressed_pair : private __compressed_pair_elem<_T1, 0>,
                          private __compressed_pair_elem<_T2, 1> {/*略*/}
template <class _Tp, int _Idx,
          bool _CanBeEmptyBase =
              is_empty<_Tp>::value && !__libcpp_is_final<_Tp>::value>
struct __compressed_pair_elem{
  typedef _Tp _ParamT;
  typedef _Tp& reference;
  typedef const _Tp& const_reference;
//函数略
	private:
  _Tp __value_;
}
template <class _Tp, int _Idx>
struct __compressed_pair_elem<_Tp, _Idx, true>{
	typedef _Tp _ParamT;
  typedef _Tp& reference;
  typedef const _Tp& const_reference;
  typedef _Tp __value_type;
}

可以发现如果_CanBeEmptyBase特化为true时候,__compressed_pair_elem会特化为__compressed_pair_elem<_Tp, _Idx, true>,两者区别就是类中是否实际存储_Tp _value

接着看下函数调用,也就是重载(),这里就是真正执行function的方法了。

_LIBCPP_INLINE_VISIBILITY
    _Rp operator()(_ArgTypes&&... __arg)
    {
        typedef __invoke_void_return_wrapper<_Rp> _Invoker;
        return _Invoker::__call(__f_.first(),
                                _VSTD::forward<_ArgTypes>(__arg)...);
    }

template <class _Ret, bool = is_void<_Ret>::value>
struct __invoke_void_return_wrapper{}

template <class _Ret>
struct __invoke_void_return_wrapper<_Ret, true>{}

__invoke_void_return_wrapper<_Rp>是一个封装了std::__invoke的类,并且有两种特化,一种是有返回值,一种是无返回值。std::__invoke是一个调用可调用对象的函数,比如函数指针,function,lambda对象都可以通过invok进行调用。

__policy_func

不知道大家看到这里还记不得的__value_func,而__policy_func是一个对非平凡类型的小对象优化的__value_func。具体大小为小于 2 * sizeof(void*)。我们来看下代码。

template <class _Fp> class __policy_func;

template <class _Rp, class... _ArgTypes> class __policy_func<_Rp(_ArgTypes...)>
{
    // Inline storage for small objects.
    __policy_storage __buf_;

    // Calls the value stored in __buf_. This could technically be part of
    // policy, but storing it here eliminates a level of indirection inside
    // operator().
    typedef __function::__policy_invoker<_Rp(_ArgTypes...)> __invoker;
    __invoker __invoker_;

    // The policy that describes how to move / copy / destroy __buf_. Never
    // null, even if the function is empty.
    const __policy* __policy_;
	//函数略
	}

可以发现思路与之前__value_func差不多,首先存储对象从由两个变成一个__policy_storage,节省了空间,__policy_storage类型其实是一个union。

union __policy_storage
{
    mutable char __small[sizeof(void*) * 2];
    void* __large;
};

接着看构造。

_LIBCPP_INLINE_VISIBILITY __policy_func(_Fp&& __f, const _Alloc& __a)
        : __policy_(__policy::__create_empty())
    {
        typedef __alloc_func<_Fp, _Alloc, _Rp(_ArgTypes...)> _Fun;
        typedef allocator_traits<_Alloc> __alloc_traits;
        typedef typename __rebind_alloc_helper<__alloc_traits, _Fun>::type
            _FunAlloc;

        if (__function::__not_null(__f))
        {
            __invoker_ = __invoker::template __create<_Fun>();
            __policy_ = __policy::__create<_Fun>();

            _FunAlloc __af(__a);
            if (__use_small_storage<_Fun>())
            {
                ::new ((void*)&__buf_.__small)
                    _Fun(_VSTD::move(__f), _Alloc(__af));
            }
            else
            {
                typedef __allocator_destructor<_FunAlloc> _Dp;
                unique_ptr<_Fun, _Dp> __hold(__af.allocate(1), _Dp(__af, 1));
                ::new ((void*)__hold.get())
                    _Fun(_VSTD::move(__f), _Alloc(__af));
                __buf_.__large = __hold.release();
            }
        }
    }

其中利用了__use_small_storage<_Fun>()判断是在自身buf上构造还是调用分配器,我们看下这个具体是个什么

template <typename _Fun>
struct __use_small_storage
    : public integral_constant<
          bool, sizeof(_Fun) <= sizeof(__policy_storage) &&
                    _LIBCPP_ALIGNOF(_Fun) <= _LIBCPP_ALIGNOF(__policy_storage) &&
                    is_trivially_copy_constructible<_Fun>::value &&
                    is_trivially_destructible<_Fun>::value> {};

可以看出来是做了类型萃取,()调用实际上是调用integral_constant的()重载。条件为小于__policy_storage的大小。对其要求小于等于,并且定义拷贝构造和析构。

满足以上条件才在自身构造。而()重载和__value_func基本一样,利用std::invoke就不多说了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值