我最近发现国内中文网站上对于STL的一些魔法基本未做任何介绍,基本上都是不知所云的一概而过。因此接下来详细分析一些比较复杂的STL代码,本篇文章分析rebind
,所有代码均摘自allocator::rebind
模板类。
rebind 的用法
rebind
用于为与正在实现的容器的元素类型不同的类型分配内存。摘自 这篇 MSDN 文章:
例如,给定一个类型为 A 的分配器对象 al,您可以使用以下表达式分配类型为 _Other 的对象:
A::rebind<Other>::other(al).allocate(1, (Other *)0)
- 1
或者,您可以通过编写类型来命名其指针类型:
A::rebind<Other>::other::pointer
- 1
还有一种比较合理的解释:
rebind 的意义就在于实现两个不同但两者互相有关的类型(比如类型 T 和 Node类型),使用同一种内存分配方法。
如果抛开rebind
不提,想要实现上述的意义,容器必须要让 allocator 是同一个模板,问题就出在容器并不关心你的allocator
是怎么写的,他唯一有关的就是在声明时在 template 中写alloc = allocator<T>
,只知道模板参数名 allocator,而不知道其具体实现,导致没有办法让 T 与 U 的 allocator 是同一个。
于是在allocator<T>
中创建一个 U 的allocator
,标准中有这样的规定:
对于allocator<T>
与一个类型 U,allocator<U>
与allocator<T>::rebind<U>::other
是等价的。
在想使用allocator<U>
的时候就需要使用allocator<T>::rebind<U>::other
,否则就是用了一个别的 allocator 了。
一个明确的例子:
比如说我们构造一个
std::list<int, myAlloc<int>>
,分配器myAlloc<int>
用于分配int
类型的对象。但是std::list<int, myAlloc<int>>
内部实际储存的是节点(list 就是双向链表),因此实际上需要分配某种节点类型(node)的对象。std::list<int, myAlloc<int>>
需要通过策略myAlloc
来分配节点类型(node)对象,也就是说需要myAlloc<node>>
。
比较复杂的应用
位于标准库 stl_list.h
的 _List_base
类有几句非常抽象的话。
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
rebind<_Tp>::other _Tp_alloc_type;
typedef __gnu_cxx::__alloc_traits<_Tp_alloc_type> _Tp_alloc_traits;
typedef typename _Tp_alloc_traits::template
rebind<_List_node<_Tp> >:: other _Node_alloc_type;
typedef __gnu_cxx::__alloc_traits<_Node_alloc_type> _Node_alloc_traits;
先分析第一句,首先查找 __alloc_traits
模板类,有
template<typename _Alloc, typename = typename _Alloc::value_type>
struct __alloc_traits{
...
template<typename _Tp>
struct rebind
{ typedef typename _Base_type:: template rebind_alloc<_Tp> other; };
}
因此,typename __gnu_cxx::__alloc_traits<_Alloc>::template rebind<_Tp>::other
等价于typename _Base_type::template rebind_alloc<_Tp>
接下来查找 _Base_type
的模板类的定义,得到
typedef std::allocator_traits<_Alloc> _Base_type;
因此上述语句等价于std::allocator_traits<_Alloc>::template rebind_alloc<_Tp>
接下来寻找 std::allocator_traits
的定义,该模板类有一个偏特化版本,用于萃取 std::allocator
,在该模板类中,有
template<typename _Up>
using rebind_alloc = allocator<_Up>;
因此,第一句可以看作是allocator<_Tp>
总结:
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template rebind<_Tp>::other _Tp_alloc_type;
等价于allocator<_Tp> _Tp_alloc_type;
第一句话的含义是使用默认分配器 std::allocator
来对 _Tp
(通常是 std::list<_Tp>
)分配内存。
同理
总结:
typedef typename _Tp_alloc_traits::template rebind<_List_node<_Tp> >:: other _Node_alloc_type;
等价于allocator<_List_node<_Tp>> _Node_alloc_type;
第三句话的含义是使用默认分配器 std::allocator
来对 _List_node<_Tp>
(通常是 std::list<_Tp>
中的节点对象)分配内存。