Notas de lectura del código fuente STL (3) - Función

prefacio

"Después de simplemente mirar la lista y deque en los últimos días, descubrí que la implementación básica es básicamente similar al análisis del código fuente de stl. Dado que stl es un tipo grande, planeo echar un vistazo selectivo. Algunos adaptadores como cola y La pila también echa un vistazo aproximado. No miré con cuidado. Simplemente sucede que la función se usa con más frecuencia recientemente, así que echemos un vistazo a la implementación de la función esta vez.

Versión STL

La versión stl utilizada en este artículo es libc++ 13.0, que pertenece al proyecto LLVM.

Proyecto LLVM Github

Si encuentra definiciones de macro desconocidas, puede consultar el documento Macros de visibilidad de símbolos

función

Primero mire la definición de función. Dado que la función usa plantillas de parámetros variables, pero las plantillas de parámetros variables no están permitidas en la declaración, la declaración se omite directamente.

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

Primero explique los parámetros de la plantilla, _Rp es el tipo de devolución de la función y _ArgTypes es el tipo de parámetro, que es un parámetro variable.

En cuanto a los dos objetos heredados debido a razones históricas, deben ser compatibles con el código anterior. Generalmente, cuando el parámetro de función es uno, heredará la especialización de __maybe_derive_from_unary_function, y los dos heredarán __maybe_derive_from_binary_function, que se eliminará después c ++ 17. Discutido.

El siguiente es el núcleo de la función.

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

__f_ es donde se almacena realmente la función, normalmente es __function::__value_func, pero un tipo grande la optimizó más tarde e hizo __function::__policy_func para objetos pequeños no triviales de menos de 2 * sizeof(void*) Se ha optimizado, pero lo que contiene se revelará después de leer la función.

//__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

Si solo está interesado, también puede echar un vistazo a la confirmación, [PARCHE] D55045: Agregar una versión de std::function que incluye algunas optimizaciones.

Veamos el constructor de la función.

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

Primero, ajuste la declaración de uno de los constructores más típicos, y puede encontrar que la estructura de la función es una función de plantilla, el primer parámetro de plantilla es una función, como function<void()>, _Fp is void(), y el segundo parámetro de plantilla es Check, verifica si el primer argumento es un objeto invocable.

Use el segundo parámetro de plantilla de __callable para especializarse y _IsNotSame usa la especialización de plantilla para determinar si los dos parámetros de plantilla son iguales. Puedes ver el código original.

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

Los dos parámetros de la plantilla se especializarán como el segundo y se especializarán como el primero si son diferentes.__uncvref_t usa la extracción de tipos para eliminar la referencia. Así que aquí is_ame es para juzgar si _Fp es un tipo de función y usar __invokable<_Fp, _ArgTypes...> para juzgar si es un objeto invocable.

Cuando _Fp no es un tipo de función y es un objeto invocable, __callable se especializa como struct __callable<_Fp, true>.

A continuación nos fijamos en la definición del constructor.

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

Solo se delega a __f_. Por cierto, la llamada () y la destrucción son casi lo mismo.

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

Primero, hay dos miembros _value_func , uno es _buf de tipo pod y el otro es _f de _base pointer . Primero veamos qué es __buf_.

El tipo de __buf_ es almacenamiento_alineado<3 * sizeof(void*)>::type. La función de la clase de almacenamiento_alineado es proporcionar un tipo de POD con el tamaño de un parámetro de plantilla. Para obtener más información, consulte almacenamiento_alineado aquí .

__base<_Rp(_ArgTypes…)> es una clase base virtual, y lo que en realidad se almacena es __func<_Fp, _Alloc, _Rp(_ArgTypes…)>, estamos hablando de esto, veamos primero la barra constructora y destructora.

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

Se puede encontrar que cuando el funtor entrante tiene menos de 3 sizeof(void ), es decir, 24 bytes, se almacenará en su propio __buf_, de lo contrario, se usará el asignador para asignar espacio. Y cuando se usa el asignador, unique_ptr también se usa para la construcción. La ventaja de usar unique_ptr es la conveniencia. Por ejemplo, si se lanza una excepción durante el proceso de construcción, unique_ptr llamará automáticamente al destructor.

La siguiente es la llamada sobrecargada (), que en realidad es la sobrecarga () que llama al __f_ interno.

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

__func y __base

__func es la clase base de __base, y __base es una clase base virtual, que especifica las funciones virtuales que deben implementar las subclases.

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

Y __func_ almacena un __alloc_func dentro

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

Llegados a este punto, casi llegaremos al fondo de la función, veamos qué hay en __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;
	//函数略
	}

Se puede encontrar que __alloc_func tiene la clase __compressed_pair, que es una buena optimización.__compressed_pair se puede entender como un par, pero ahorra espacio cuando uno o todos están vacíos, porque puedes probar una clase vacía de sizeof, y puede encontrar Una clase vacía tiene tamaño 1. Aquí está el uso de la herencia para omitir el espacio de las clases vacías. Veamos __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;
}

Se puede encontrar que si _CanBeEmptyBase se especializa en verdadero, __compressed_pair_elem se especializará en __compressed_pair_elem<_Tp, _Idx, true>, la diferencia entre los dos es si el valor _Tp _ está realmente almacenado en la clase .

Luego mire la llamada a la función, es decir, la sobrecarga (), esta es la forma de ejecutar la función.

_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> es una clase que encapsula std::__invoke y tiene dos especializaciones, una con valor de retorno y otra sin valor de retorno. std::__invoke es una función que llama a objetos invocables, como punteros de función, funciones y objetos lambda, todos pueden llamarse a través de invok.

__policy_func

No conozco la __value_func que no puedes recordar cuando la ves aquí, y __policy_func es una __value_func optimizada para objetos pequeños de tipos no triviales. El tamaño específico es inferior a 2 * sizeof(void*). Veamos el código.

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

Se puede encontrar que la idea es similar a la anterior __value_func. Primero, el objeto de almacenamiento cambia de dos a uno __policy_storage, lo que ahorra espacio. El tipo __policy_storage es en realidad una unión.

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

Luego mira la estructura.

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

Entre ellos, __use_small_storage<_Fun>() se usa para juzgar si construir o llamar al asignador en su propio buf. Veamos qué es esto específicamente

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

Se puede ver que la extracción de tipos está hecha, y la llamada () en realidad está llamando a la sobrecarga () de integral_constant. La condición es menor que el tamaño de __policy_storage. Requiere menor o igual que, y define la construcción y destrucción de copias.

Solo cuando se cumplen las condiciones anteriores se puede construir por sí mismo. La sobrecarga de () es básicamente lo mismo que __value_func, por lo que no diré mucho sobre el uso de std::invoke.

Supongo que te gusta

Origin blog.csdn.net/ninesnow_c/article/details/122128276
Recomendado
Clasificación