《STL源码分析》学习笔记 — 空间配置器 — allocator_traits
一、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
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_arg 是 ptr_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
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