《STL源码分析》学习笔记 — 空间配置器 — allocator_traits

news/2024/7/20 15:42:08 标签: c++, STL源码, 内存管理, allocator

STL源码分析》学习笔记 — 空间配置器 — allocator_traits

  • 一、traits编程技法
  • 二、基类 __allocator_traits_base
  • 三、allocator_traits
    • 1、类型及属性定义
      • (1)__detected_or_t
      • (2)_Ptr _Diff _Size
    • 2、外部方法
    • 3、内部方法
  • 四、allocator_traits 部分特化
  • 五、其余方法
    • 1、__alloc_on_copy
    • 2、__alloc_on_move
    • 3、__alloc_on_swap
    • 4、类型支持
      • (1)__is_alloc_insertable_impl
      • (2)__is_copy_insertable
      • (3)__is_move_insertable
      • (4)__is_allocator
      • (5)_RequireAllocator
    • 5、_Destroy
  • 六、总结

一、traits编程技法

trait 有特征特质的意思。使用 traits 既是一种特征提取,也是为了适配不同的接口。试想不同的空间配置器可能有不同的接口,有些可能是来自古老的实现。如何使用户在使用这些配置器时,不需要考虑它们接口的差异而仅需要考虑上层的实现呢?使用 traits。最直观的例子是针对迭代器的算法中通过 iterator_traits 对指针的特性提取:

template<typename _InputIterator, typename _OutputIterator, typename _Predicate>
_GLIBCXX20_CONSTEXPR _OutputIterator copy_if(_InputIterator __first, _InputIterator __last,
                                             _OutputIterator __result, _Predicate __pred)
{
    // concept requirements
    __glibcxx_function_requires(_InputIteratorConcept<_InputIterator>)
            __glibcxx_function_requires(_OutputIteratorConcept<_OutputIterator,
                                        typename iterator_traits<_InputIterator>::value_type>)
            __glibcxx_function_requires(_UnaryPredicateConcept<_Predicate,
                                        typename iterator_traits<_InputIterator>::value_type>)
            __glibcxx_requires_valid_range(__first, __last);
    
    ...
}

我们知道容器的迭代器都各自声明了 value_type 属性,因此 iterator_traits<_InputIterator>::value_type> 直接声明容器对应的 value_type。那么对指针,这个算法是怎么起作用的呢?答案是偏特化。

template<typename _Tp>
#if __cpp_concepts >= 201907L
requires is_object_v<_Tp>
#endif
struct iterator_traits<_Tp*>
{
	...
}

这个版本中提供了指针类型相对应的所有满足迭代器要求的接口和属性。这正是 traits 的作用所在。

allocator_traits_base_31">二、基类 __allocator_traits_base

struct __allocator_traits_base
{
    template<typename _Tp, typename _Up, typename = void>
    struct __rebind : __replace_first_arg<_Tp, _Up> { };

    template<typename _Tp, typename _Up>
    struct __rebind<_Tp, _Up, __void_t<typename _Tp::template rebind<_Up>::other>>
    { using type = typename _Tp::template rebind<_Up>::other; };

protected:
    template<typename _Tp>
    using __pointer = typename _Tp::pointer;
    template<typename _Tp>
    using __c_pointer = typename _Tp::const_pointer;
    template<typename _Tp>
    using __v_pointer = typename _Tp::void_pointer;
    template<typename _Tp>
    using __cv_pointer = typename _Tp::const_void_pointer;
    template<typename _Tp>
    using __pocca = typename _Tp::propagate_on_container_copy_assignment;
    template<typename _Tp>
    using __pocma = typename _Tp::propagate_on_container_move_assignment;
    template<typename _Tp>
    using __pocs = typename _Tp::propagate_on_container_swap;
    template<typename _Tp>
    using __equal = typename _Tp::is_always_equal;
};

template<typename _Alloc, typename _Up>
using __alloc_rebind = typename __allocator_traits_base::template __rebind<_Alloc, _Up>::type;

这里的受保护成员中的 template 关键字表示这些使用了别名模板语法(C++11引入的新功能)。这种语法用于为与某个类型相关联的类型取别名,具体类型取决于模板参数。注意此基类并非模板类。

__rebind 为模板类,并且有一个部分特化的版本。当模板参数 _Tp 类中含有 rebind 模板类并且有 other 类型时(也就是某个空间配置器类型),其实例化将为此部分特化版本的实例化,其 type 类型定义为 rebind<_Up>::other。否则,其实例化为普通版本的实例化,继承自 __replace_first_arg__replace_first_argptr_traits.h 中的模板类:

// Given Template<T, ...> and U return Template<U, ...>, otherwise invalid.
template<typename _Tp, typename _Up>
struct __replace_first_arg
{ };

template<template<typename, typename...> class _Template, typename _Up,
         typename _Tp, typename... _Types>
struct __replace_first_arg<_Template<_Tp, _Types...>, _Up>
{ using type = _Template<_Up, _Types...>; };

当第一个参数为模板类时,此结构体的功能正如其名字所示,将 __replace_first_arg 的第二个参数设置为 _Template 的第一个模板参数。否则,该模板类没有 type 属性。

通过继承自 __replace_first_arg__rebind 模板类可以支持针对内部不包含 rebind 模板类的空间配置器(也就是C++20及以后的 new_allocator 等)。其默认第一个参数为配置器分配的数据类型,因此替换的是第一个参数。

allocator_traits_81">三、allocator_traits

allocator_traits 定义比较长,我们拆成两部分看。

1、类型及属性定义

/**
   * @brief  Uniform interface to all allocator types.
   * @ingroup allocators
  */
template<typename _Alloc>
struct allocator_traits : __allocator_traits_base
{
    typedef _Alloc allocator_type;
    typedef typename _Alloc::value_type value_type;
    using pointer = __detected_or_t<value_type*, __pointer, _Alloc>;

private:
    // Select _Func<_Alloc> or pointer_traits<pointer>::rebind<_Tp>
    template<template<typename> class _Func, typename _Tp, typename = void>
    struct _Ptr
    {
        using type = typename pointer_traits<pointer>::template rebind<_Tp>;
    };

    template<template<typename> class _Func, typename _Tp>
    struct _Ptr<_Func, _Tp, __void_t<_Func<_Alloc>>>
    {
        using type = _Func<_Alloc>;
    };

    // Select _A2::difference_type or pointer_traits<_Ptr>::difference_type
    template<typename _A2, typename _PtrT, typename = void>
    struct _Diff
    { using type = typename pointer_traits<_PtrT>::difference_type; };

    template<typename _A2, typename _PtrT>
    struct _Diff<_A2, _PtrT, __void_t<typename _A2::difference_type>>
    { using type = typename _A2::difference_type; };

    template<typename _A2, typename _DiffT, typename = void>
    struct _Size : make_unsigned<_DiffT> { };

    template<typename _A2, typename _DiffT>
    struct _Size<_A2, _DiffT, __void_t<typename _A2::size_type>>
    { using type = typename _A2::size_type; };

public:
    using const_pointer = typename _Ptr<__c_pointer, const value_type>::type;
    using void_pointer = typename _Ptr<__v_pointer, void>::type;
    using const_void_pointer = typename _Ptr<__cv_pointer, const void>::type;
    using difference_type = typename _Diff<_Alloc, pointer>::type;
    using size_type = typename _Size<_Alloc, difference_type>::type;
    using propagate_on_container_copy_assignment = __detected_or_t<false_type, __pocca, _Alloc>;
    using propagate_on_container_move_assignment = __detected_or_t<false_type, __pocma, _Alloc>;
    using propagate_on_container_swap = __detected_or_t<false_type, __pocs, _Alloc>;
    using is_always_equal = __detected_or_t<typename is_empty<_Alloc>::type, __equal, _Alloc>;

    template<typename _Tp>
    using rebind_alloc = __alloc_rebind<_Alloc, _Tp>;
    template<typename _Tp>
    using rebind_traits = allocator_traits<rebind_alloc<_Tp>>;
}

(1)__detected_or_t

allocator_traits 频繁使用 __detected_or_t 来定义属性和方法。其实现如下:

/// Implementation of the detection idiom (negative case).
template<typename _Default, typename _AlwaysVoid, template<typename...> class _Op, typename... _Args>
struct __detector
{
    using value_t = false_type;
    using type = _Default;
};

/// Implementation of the detection idiom (positive case).
template<typename _Default, template<typename...> class _Op, typename... _Args>
struct __detector<_Default, __void_t<_Op<_Args...>>, _Op, _Args...>
{
    using value_t = true_type;
    using type = _Op<_Args...>;
};

// Detect whether _Op<_Args...> is a valid type, use _Default if not.
template<typename _Default, template<typename...> class _Op, typename... _Args>
using __detected_or = __detector<_Default, void, _Op, _Args...>;

// _Op<_Args...> if that is a valid type, otherwise _Default.
template<typename _Default, template<typename...> class _Op, typename... _Args>
using __detected_or_t = typename __detected_or<_Default, _Op, _Args...>::type;

不难看出,其实现方法是通过区分 __void_t<_Op<_Args…>> 是否为有效表达式:即第二个参数(模板类参数)能否以第三个参数为模板参数进行实例化。如果可以,则将根据部分实例化的 __detector 进行处理;否则,根据普通版本进行处理。第一个参数指明了普通版本的实例化中 type 类型被实例化为何种类型。举个例子,对于定义第三行 pointer 来说(__pointer 定义在基类中),如果空间配置器内含 pointer 类型,则使用它;否则使用 value_type*。

(2)_Ptr _Diff _Size

_Ptr 模板类是借助 pointer_traits 实现的。类似地,如果 _Func<_Alloc>> 可以被实例化,则 type 将被定义为此类型;否则将借助前面定义的 pointer,使用 pointer_traits::rebind 实现重新绑定(此内部模板类的语义与 allocator 中的 rebind 类似)。因此,const_pointervoid_pointerconst_void_pointer 都是通过此结构体定义:如果空间配置器中有相应类型,则使用空间配置器内的定义 ;否则,使用 pointer 进行重新绑定。
_Diff_Size 都类似,都是为了支持不包含相应类型定义的空间配置器。这也是为何 new_allocator 中去掉了很多属性和类型定义。同时,这也符合复用的思想:STL 内部都是通过 allocator_traits 访问配置器,因此可以将共有功能提供到该特性萃取类中实现。以后我们也最好使用此模板类调用空间配置器

2、外部方法

template<typename _Alloc>
struct allocator_traits : __allocator_traits_base
{
public:
    _GLIBCXX_NODISCARD static _GLIBCXX20_CONSTEXPR pointer
    allocate(_Alloc& __a, size_type __n)
    { return __a.allocate(__n); }

    _GLIBCXX_NODISCARD static _GLIBCXX20_CONSTEXPR pointer
    allocate(_Alloc& __a, size_type __n, const_void_pointer __hint)
    { return _S_allocate(__a, __n, __hint, 0); }

    static _GLIBCXX20_CONSTEXPR void
    deallocate(_Alloc& __a, pointer __p, size_type __n)
    { __a.deallocate(__p, __n); }

    template<typename _Tp, typename... _Args>
    static _GLIBCXX20_CONSTEXPR auto
    construct(_Alloc& __a, _Tp* __p, _Args&&... __args)
    noexcept(noexcept(_S_construct(__a, __p,
                                   std::forward<_Args>(__args)...)))
    -> decltype(_S_construct(__a, __p, std::forward<_Args>(__args)...))
    { _S_construct(__a, __p, std::forward<_Args>(__args)...); }

    template<typename _Tp>
    static _GLIBCXX20_CONSTEXPR void
    destroy(_Alloc& __a, _Tp* __p)
    noexcept(noexcept(_S_destroy(__a, __p, 0)))
    { _S_destroy(__a, __p, 0); }

    static _GLIBCXX20_CONSTEXPR size_type
    max_size(const _Alloc& __a) noexcept
    { return _S_max_size(__a, 0); }

    static _GLIBCXX20_CONSTEXPR _Alloc
    select_on_container_copy_construction(const _Alloc& __rhs)
    { return _S_select(__rhs, 0); }
};

这些外部方法大多是通过直接调动内部函数实现的。值得注意的是,allocate 的单参数版本和 deallocate 方法是直接通过调用空间配置器的相应版本实现的;除了 _S_construct 是直接转发参数以外,其他方法都增加了一个参数0。

3、内部方法

template<typename _Alloc>
struct allocator_traits : __allocator_traits_base
{
private:
    template<typename _Alloc2>
    static constexpr auto
    _S_allocate(_Alloc2& __a, size_type __n, const_void_pointer __hint, int)
    -> decltype(__a.allocate(__n, __hint))
    { return __a.allocate(__n, __hint); }
    
    template<typename _Alloc2>
    static constexpr pointer
    _S_allocate(_Alloc2& __a, size_type __n, const_void_pointer, ...)
    { return __a.allocate(__n); }

    template<typename _Tp, typename... _Args>
    struct __construct_helper
    {
        template<typename _Alloc2,
                 typename = decltype(std::declval<_Alloc2*>()->construct(
                 std::declval<_Tp*>(), std::declval<_Args>()...))>
        static true_type __test(int);

        template<typename>
        static false_type __test(...);

        using type = decltype(__test<_Alloc>(0));
    };

    template<typename _Tp, typename... _Args>
    using __has_construct
    = typename __construct_helper<_Tp, _Args...>::type;

    template<typename _Tp, typename... _Args>
    static _GLIBCXX14_CONSTEXPR _Require<__has_construct<_Tp, _Args...>>
    _S_construct(_Alloc& __a, _Tp* __p, _Args&&... __args)
    noexcept(noexcept(__a.construct(__p, std::forward<_Args>(__args)...)))
    { __a.construct(__p, std::forward<_Args>(__args)...); }

    template<typename _Tp, typename... _Args>
    static _GLIBCXX14_CONSTEXPR
    _Require<__and_<__not_<__has_construct<_Tp, _Args...>>,
    is_constructible<_Tp, _Args...>>>
    _S_construct(_Alloc&, _Tp* __p, _Args&&... __args)
    noexcept(std::is_nothrow_constructible<_Tp, _Args...>::value)
    {
#if __cplusplus <= 201703L
        ::new((void*)__p) _Tp(std::forward<_Args>(__args)...);
#else
        std::construct_at(__p, std::forward<_Args>(__args)...);
#endif
    }

    template<typename _Alloc2, typename _Tp>
    static _GLIBCXX14_CONSTEXPR auto
    _S_destroy(_Alloc2& __a, _Tp* __p, int)
    noexcept(noexcept(__a.destroy(__p)))
    -> decltype(__a.destroy(__p))
    { __a.destroy(__p); }

    template<typename _Alloc2, typename _Tp>
    static _GLIBCXX14_CONSTEXPR void
    _S_destroy(_Alloc2&, _Tp* __p, ...)
    noexcept(std::is_nothrow_destructible<_Tp>::value)
    { std::_Destroy(__p); }

    template<typename _Alloc2>
    static constexpr auto
    _S_max_size(_Alloc2& __a, int)
    -> decltype(__a.max_size())
    { return __a.max_size(); }

	// 这个函数貌似没有调用
    template<typename _Alloc2>
    static constexpr size_type
    _S_max_size(_Alloc2&, ...)
    {
        return __gnu_cxx::__numeric_traits<size_type>::__max / sizeof(value_type);
    }

    template<typename _Alloc2>
    static constexpr auto
    _S_select(_Alloc2& __a, int)
    -> decltype(__a.select_on_container_copy_construction())
    { return __a.select_on_container_copy_construction(); }

    template<typename _Alloc2>
    static constexpr _Alloc2
    _S_select(_Alloc2& __a, ...)
    { return __a; }
};

这些内部函数基本都区分了最后的参数为 int 或可变参数两个版本。int 版本是直接将函数调用转发给空间配置器,因此完全由空间配置器实现者决定它们的功能。那么什么情况下调用可变参数版本呢?仔细观察,我们会发现每个 int 版本方法的实例化都使用了 decltype 指明其返回值类型。因此,如果想要实例化此版本,必须要保证 decltype 表达式有效。综上,外部函数调用内部函数增加了一个 int 参数的意义在于:如果相应的 decltype 表达式成立,那么两个模板都是可行函数原型,但是第一个模板匹配程度更高,因此会选择第一个实例化;如果不成立,则将调用第二个进行实例化,此时的 int 将作为可变参数传入

这里稍微有点复杂的就是构造功能了。首先,这里定义了一个模板类 __construct_helper,其内部定义了 __test 模板类。当 std::declval<…>()->construct(…) 为有效表达式时该模板类将被定义为 true_type,否则为 false_type。那么什么时候此表达式有效呢?首先其通过 declval 得到的是一个 _Alloc2*,也就是配置器对象指针。然后通过此指针调用 construct 方法。也就是说,其与前面一致,当配置器中有相应的方法时,调用该方法;否则,这里将会调用 new(C++17及以前)或 construct_at(C++20及以后),当然前提是,相应的参数能够被用来构造其类型。

allocator_traits__313">四、allocator_traits 部分特化

allocator_traits 有一个针对 allocator 的特化版本:

/// Partial specialization for std::allocator.
template<typename _Tp>
struct allocator_traits<allocator<_Tp>>
{
    using allocator_type = allocator<_Tp>;
    using value_type = _Tp;
    using pointer = _Tp*;
    using const_pointer = const _Tp*;
    using void_pointer = void*;
    using const_void_pointer = const void*;
    using difference_type = std::ptrdiff_t;
    using size_type = std::size_t;
    using propagate_on_container_copy_assignment = false_type;
    using propagate_on_container_move_assignment = true_type;
    using propagate_on_container_swap = false_type;
    using is_always_equal = true_type;

    template<typename _Up>
    using rebind_alloc = allocator<_Up>;

    template<typename _Up>
    using rebind_traits = allocator_traits<allocator<_Up>>;

    _GLIBCXX_NODISCARD static _GLIBCXX20_CONSTEXPR pointer
    allocate(allocator_type& __a, size_type __n)
    { return __a.allocate(__n); }

    _GLIBCXX_NODISCARD static _GLIBCXX20_CONSTEXPR pointer
    allocate(allocator_type& __a, size_type __n, const_void_pointer __hint)
    {
#if __cplusplus <= 201703L
        return __a.allocate(__n, __hint);
#else
        return __a.allocate(__n);
#endif
    }
    
    static _GLIBCXX20_CONSTEXPR void
    deallocate(allocator_type& __a, pointer __p, size_type __n)
    { __a.deallocate(__p, __n); }
    
    template<typename _Up, typename... _Args>
    static _GLIBCXX20_CONSTEXPR void
    construct(allocator_type& __a __attribute__((__unused__)), _Up* __p,
              _Args&&... __args)
    noexcept(std::is_nothrow_constructible<_Up, _Args...>::value)
    {
#if __cplusplus <= 201703L
        __a.construct(__p, std::forward<_Args>(__args)...);
#else
        std::construct_at(__p, std::forward<_Args>(__args)...);
#endif
    }

    template<typename _Up>
    static _GLIBCXX20_CONSTEXPR void
    destroy(allocator_type& __a __attribute__((__unused__)), _Up* __p)
    noexcept(is_nothrow_destructible<_Up>::value)
    {
#if __cplusplus <= 201703L
        __a.destroy(__p);
#else
        std::destroy_at(__p);
#endif
    }

    static _GLIBCXX20_CONSTEXPR size_type
    max_size(const allocator_type& __a __attribute__((__unused__))) noexcept
    {
#if __cplusplus <= 201703L
        return __a.max_size();
#else
        return size_t(-1) / sizeof(value_type);
#endif
    }

    static _GLIBCXX20_CONSTEXPR allocator_type
    select_on_container_copy_construction(const allocator_type& __rhs)
    { return __rhs; }
};

其与普通版本的主要区别在于 constructdestroy 内部实现所调用的函数(以后此特化版本可能会被取代)。

五、其余方法

1、__alloc_on_copy

#if __cplusplus < 201703L
template<typename _Alloc>
inline void
__do_alloc_on_copy(_Alloc& __one, const _Alloc& __two, true_type)
{ __one = __two; }

template<typename _Alloc>
inline void
__do_alloc_on_copy(_Alloc&, const _Alloc&, false_type)
{ }
#endif

template<typename _Alloc>
_GLIBCXX14_CONSTEXPR inline void
__alloc_on_copy(_Alloc& __one, const _Alloc& __two)
{
    typedef allocator_traits<_Alloc> __traits;
    typedef typename __traits::propagate_on_container_copy_assignment __pocca;
#if __cplusplus >= 201703L
    if constexpr (__pocca::value)
            __one = __two;
#else
    __do_alloc_on_copy(__one, __two, __pocca());
#endif
}

template<typename _Alloc>
constexpr _Alloc
__alloc_on_copy(const _Alloc& __a)
{
    typedef allocator_traits<_Alloc> __traits;
    return __traits::select_on_container_copy_construction(__a);
}

第一个版本的 __alloc_on_copy 根据空间配置器是否支持 propagate_on_container_copy_assignment 决定其实现。如果支持,则调用赋值函数将源空间配置器对象赋值给目标空间配置器对象;否则,什么都不做。

第二个版本的 __alloc_on_copy 通过调用 select_on_container_copy_construction 返回一个空间配置器的拷贝。

2、__alloc_on_move

#if __cplusplus < 201703L
template<typename _Alloc>
inline void __do_alloc_on_move(_Alloc& __one, _Alloc& __two, true_type)
{ __one = std::move(__two); }

template<typename _Alloc>
inline void __do_alloc_on_move(_Alloc&, _Alloc&, false_type)
{ }
#endif

template<typename _Alloc>
_GLIBCXX14_CONSTEXPR inline void
__alloc_on_move(_Alloc& __one, _Alloc& __two)
{
    typedef allocator_traits<_Alloc> __traits;
    typedef typename __traits::propagate_on_container_move_assignment __pocma;
#if __cplusplus >= 201703L
    if constexpr (__pocma::value)
            __one = std::move(__two);
#else
    __do_alloc_on_move(__one, __two, __pocma());
#endif
}

此函数实现的是移动语义,当空间配置器支持 propagate_on_container_move_assignment 时调用移动赋值函数。

3、__alloc_on_swap

#if __cplusplus < 201703L
template<typename _Alloc>
inline void __do_alloc_on_swap(_Alloc& __one, _Alloc& __two, true_type)
{
    using std::swap;
    swap(__one, __two);
}

template<typename _Alloc>
inline void __do_alloc_on_swap(_Alloc&, _Alloc&, false_type)
{ }
#endif

template<typename _Alloc>
_GLIBCXX14_CONSTEXPR inline void
__alloc_on_swap(_Alloc& __one, _Alloc& __two)
{
    typedef allocator_traits<_Alloc> __traits;
    typedef typename __traits::propagate_on_container_swap __pocs;
#if __cplusplus >= 201703L
    if constexpr (__pocs::value)
    {
        using std::swap;
        swap(__one, __two);
    }
#else
    __do_alloc_on_swap(__one, __two, __pocs());
#endif
}

此函数实现的是交换功能,当空间配置器支持 propagate_on_container_swap 时调用std::swap 交换两个对象。

4、类型支持

__is_copy_insertable__is_move_insertable 指明其实现可能有问题,所以使用时需要格外注意。

(1)__is_alloc_insertable_impl

template<typename _Alloc, typename _Tp,
         typename _ValueT = __remove_cvref_t<typename _Alloc::value_type>,
         typename = void>
struct __is_alloc_insertable_impl
        : false_type
{ };

template<typename _Alloc, typename _Tp, typename _ValueT>
struct __is_alloc_insertable_impl<_Alloc, _Tp, _ValueT,
        __void_t<decltype(allocator_traits<_Alloc>::construct(
                              std::declval<_Alloc&>(), std::declval<_ValueT*>(),
                              std::declval<_Tp>()))>>
                                                      : true_type
{ };

当模板类支持 construct 方法时,且目标对象类型 _ValueT 可以通过模板参数 _Tp 构造,则认为其是可插入的。

(2)__is_copy_insertable

template<typename _Alloc>
struct __is_copy_insertable
        : __is_alloc_insertable_impl<_Alloc,
        typename _Alloc::value_type const&>::type
{ };

// std::allocator<_Tp> just requires CopyConstructible
template<typename _Tp>
struct __is_copy_insertable<allocator<_Tp>>
        : is_copy_constructible<_Tp>
{ };

对于普通版本的 __is_copy_insertable,是否支持拷贝插入取决于是否支持是否能通过 const& value_type 构造value_type 对象。对于 std::allocator 的特化版本,是否支持拷贝插入取决于其模板参数是否支持拷贝构造。

(3)__is_move_insertable

template<typename _Alloc>
struct __is_move_insertable
        : __is_alloc_insertable_impl<_Alloc, typename _Alloc::value_type>::type
{ };

// std::allocator<_Tp> just requires MoveConstructible
template<typename _Tp>
struct __is_move_insertable<allocator<_Tp>>
        : is_move_constructible<_Tp>
{ };

__is_copy_insertable 实现类似。

allocator_547">(4)__is_allocator

// Trait to detect Allocator-like types.
template<typename _Alloc, typename = void>
struct __is_allocator : false_type { };

template<typename _Alloc>
struct __is_allocator<_Alloc,
        __void_t<typename _Alloc::value_type,
        decltype(std::declval<_Alloc&>().allocate(size_t{}))>>
                                                               : true_type { };

当模板参数 _Alloc 支持 allocate 方法时,认为是一个可用的迭代器类型。

(5)_RequireAllocator

template<typename _Alloc>
using _RequireAllocator
= typename enable_if<__is_allocator<_Alloc>::value, _Alloc>::type;

template<typename _Alloc>
using _RequireNotAllocator
= typename enable_if<!__is_allocator<_Alloc>::value, _Alloc>::type;
#endif

借由 __is_allocator 实现空间配置器判断。事实上,我觉得这里应该加上个 deallocate 方法的判断。因为我们看 allocator_traits 中的实现就知道:除了 deallocateallocate 是一定要通过空间配置器本身调用外,allocator_traits 对于其余方法、属性或类型都做了自定义

5、_Destroy

/**
   * Destroy a range of objects using the supplied allocator.  For
   * non-default allocators we do not optimize away invocation of
   * destroy() even if _Tp has a trivial destructor.
   */

template<typename _ForwardIterator, typename _Allocator>
void
_Destroy(_ForwardIterator __first, _ForwardIterator __last,
         _Allocator& __alloc)
{
    for (; __first != __last; ++__first)
#if __cplusplus < 201103L
        __alloc.destroy(std::__addressof(*__first));
#else
        allocator_traits<_Allocator>::destroy(__alloc,
                                              std::__addressof(*__first));
#endif
}

template<typename _ForwardIterator, typename _Tp>
inline void
_Destroy(_ForwardIterator __first, _ForwardIterator __last,
         allocator<_Tp>&)
{
    _Destroy(__first, __last);
}

类似 stl_construct.h 中的 _Destroy 方法。这里通过 allocator_traits 实现对象的析构。

六、总结

在这里插入图片描述


http://www.niftyadmin.cn/n/1340378.html

相关文章

《数据结构、算法与应用 —— C++语言描述》学习笔记 — 队列 —— 应用 —— 工厂仿真(一)

《数据结构、算法与应用 —— C语言描述》学习笔记 — 队列 —— 应用 —— 工厂仿真&#xff08;一&#xff09;一、问题描述二、如何仿真三、工厂仿真的例子四、工厂仿真的好处五、设计六、工序和任务1、声明2、实现七、机器观察者1、声明2、实现八、机器类1、声明2、实现九、…

spring中缓存配置

缓存spring文件的配置&#xff1a;<?xml version"1.0" encoding"UTF-8"?><beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xmlns:aop"http://www.s…

《数据结构、算法与应用 —— C++语言描述》学习笔记 — 队列 —— 应用 —— 工厂仿真(二)

《数据结构、算法与应用 —— C语言描述》学习笔记 — 队列 —— 应用 —— 工厂仿真&#xff08;二&#xff09;一、数据统计与展示1、声明2、实现二、测试代码1、实现2、测试文件3、输出三、总结一、数据统计与展示 1、声明 /********************************************…

在linux跑xenomai vkworks skin的测试

1 代码 &#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff03;&#xff…

《More Effictive C++》学习笔记 — 技术(五)

《More Effictive C》学习笔记 — 技术&#xff08;五&#xff09;条款30 — proxy classes1、二维数组的表现2、区分 operator[] 的读写动作3、代理类&#xff08;1&#xff09;operator[]&#xff08;2&#xff09;代理类的读写区分4、限制&#xff08;1&#xff09;取地址符…

《数据结构、算法与应用 —— C++语言描述》学习笔记 — 字典 — 链表实现

《数据结构、算法与应用 —— C语言描述》学习笔记 — 字典 — 链表实现一、字典二、抽象数据类型三、链表描述1、节点2、接口声明3、拷贝控制接口4、容量接口5、修改接口虽然在n个元素的有序数组上二分查找所需要的时间为 O(logn)O(logn)O(logn)&#xff0c;但是有序链表上查找…

【论文阅读】SKDBERT: Compressing BERT via Stochastic Knowledge Distillation

2022-2023年论文系列之模型轻量化和推理加速 定义最新 通过Connected Papers搜索引用PaBEE/DeeBERT/FastBERT的最新工作,涵盖: 模型推理加速边缘设备应用生成模型BERT模型知识蒸馏论文目录 SmartBERT: A Promotion of Dynamic Early Exiting Mechanism for Accelerating BE…

转 javascript RSA 加密

为什么80%的码农都做不了架构师&#xff1f;>>> 用javascript与java进行RSA加密与解密 2009-09-27 22:13:12| 分类&#xff1a; java/jsp |举报 |字号 订阅 下载LOFTER客户端 …