STL source code reading notes (3) - Function

foreword

After simply looking at the list and deque in the past few days, I found that the basic implementation is basically similar to the stl source code analysis. Since stl is a big guy, I plan to take a selective look. Some adapters like queue and stack also take a rough look. I didn't look carefully. It just happens that function is used more frequently recently, so let's take a look at the implementation of function this time.

STL version

The stl version used in this article is libc++ 13.0, which belongs to the LLVM project.

LLVM project Github

If you encounter unfamiliar macro definitions, you can refer to the document Symbol Visibility Macros

function

First look at the definition of function. Since function uses variable parameter templates, but variable parameter templates are not allowed in the declaration, the declaration is skipped directly.

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

First explain the template parameters, _Rp is the return type of the function, and _ArgTypes is the parameter type, which is a variable parameter.

As for the two inherited objects due to historical reasons, they need to be compatible with the old code. Generally, when the function parameter is one, it will inherit the specialization of __maybe_derive_from_unary_function, and the two will inherit __maybe_derive_from_binary_function, which will be deleted after c++17. Discussed.

Next is the core of the 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_ is where the function is actually stored, normally it is __function::__value_func, but a big guy later optimized it and made __function::__policy_func for non-trivial small objects less than 2 * sizeof(void*) It has been optimized, but what is in it will be revealed after reading the 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

If you are just interested, you can also take a look at the commit, [PATCH] D55045: Add a version of std::function that includes a few optimizations.

Let's look at the constructor of 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);

First, adjust the declaration of one of the most typical constructors, and you can find that the structure of function is a template function, the first template parameter is a function, such as function<void()>, _Fp is void(), and the second template parameter is a Check, checks whether the first argument is a callable object.

Use the second template parameter of __callable to specialize, and _IsNotSame uses template specialization to determine whether the two template parameters are the same. You can see the original code.

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;
};

The two template parameters will be specialized as the second one, and will be specialized as the first one if they are different. __uncvref_t uses type extraction to remove the reference. So here is_same is to judge whether _Fp is a function type, and use __invokable<_Fp, _ArgTypes...> to judge whether it is a callable object.

When _Fp is not a function type and is a callable object, __callable is specialized as struct __callable<_Fp, true>.

Next we look at the definition of the constructor.

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

It's just delegated to __f_. By the way, the () call and the destruction are almost the same.

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_;

First, there are two _value_func members, one is _buf of pod type , and the other is _f of _base pointer . Let's first look at what __buf_ is.

The type of __buf_ is aligned_storage<3 * sizeof(void*)>::type. The function of the aligned_storage class is to provide a POD type with the size of a template parameter. For details, see aligned_storage here

__base<_Rp(_ArgTypes…)> is a virtual base class, and what is actually stored is __func<_Fp, _Alloc, _Rp(_ArgTypes…)>, we are talking about this, let’s first look at the constructor and destructor Bar.

 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();
    }

It can be found that when the incoming functor is less than 3 sizeof(void ), that is, 24 bytes, it will be stored in its own __buf_, otherwise the allocator will be used to allocate space. And when using the allocator, unique_ptr is also used for construction. The advantage of using unique_ptr is convenience. For example, if an exception is thrown during the construction process, unique_ptr will automatically call the destructor.

Next is the overloaded () call, which is actually the () overload that calls the internal __f_.

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

__func and __base

__func is the base class of __base, and __base is a virtual base class, which specifies the virtual functions that subclasses must implement.

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
};

And __func_ stores an __alloc_func inside

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

At this point, we will almost get to the bottom of the function. Let's see what is in __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;
	//函数略
	}

It can be found that __alloc_func has the __compressed_pair class, which is a good optimization. __compressed_pair can be understood as a pair, but it saves space when one or all of them are empty, because you can try an empty class of sizeof, and you can find An empty class has size 1. Here is the use of inheritance to omit the space of empty classes. Let's look at __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;
}

It can be found that if _CanBeEmptyBase is specialized to true, __compressed_pair_elem will be specialized to __compressed_pair_elem<_Tp, _Idx, true>, the difference between the two is whether _Tp _ value is actually stored in the class .

Then look at the function call, that is, overloading (), here is the way to actually execute the 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> is a class that encapsulates std::__invoke and has two specializations, one with return value and one without return value. std::__invoke is a function that calls callable objects, such as function pointers, function, and lambda objects can all be called through invok.

__policy_func

I don’t know the __value_func that you can’t remember when you see it here, and __policy_func is a __value_func optimized for small objects of non-trivial types. The specific size is less than 2 * sizeof(void*). Let's look at the code.

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_;
	//函数略
	}

It can be found that the idea is similar to the previous __value_func. First, the storage object changes from two to one __policy_storage, which saves space. The __policy_storage type is actually a union.

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

Then look at the structure.

_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();
            }
        }
    }

Among them, __use_small_storage<_Fun>() is used to judge whether to construct or call the allocator on its own buf. Let's see what this is specifically

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> {
    
    };

It can be seen that the type extraction is done, and the () call is actually calling the () overload of integral_constant. The condition is less than the size of __policy_storage. It requires less than or equal to, and defines copy construction and destruction.

Only when the above conditions are met can it be constructed by itself. The () overload is basically the same as __value_func, so I won’t say much about using std::invoke.

Guess you like

Origin blog.csdn.net/ninesnow_c/article/details/122128276