STL源码阅读(2)–allocator::rebind
经过前面的了解,我想应该可以正式进入STL的容器实现的阅读了,ok那就先来看看vector的实现。vector的实现代码在bits/stl_vector.h中,可以看到的是一个类_Vector_base
template<typename _Tp, typename _Alloc>
struct _Vector_base
{
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
rebind<_Tp>::other _Tp_alloc_type;
typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer
pointer;
类中首先是定义__gnu_cxx::__alloc_traits<_Alloc>::template
rebind<_Tp>::other的别名,what?rebind是什么东西?一开始便看不懂了。好吧,阅读容器实现前还是先搞懂rebind是个什么鬼。
1.来源
从_gnu_cxx::__alloc_traits<_Alloc>::template
rebind<_Tp>::other看rebind是来自模板类__alloc_traits,该类在ext/alloc_atraits.h文件中定义
#if __cplusplus >= 201103L
typedef std::allocator_traits<_Alloc> _Base_type;
template<typename _Tp>
struct rebind
{ typedef typename _Base_type::template rebind_alloc<_Tp> other; };
#else
template<typename _Tp>
struct rebind
{ typedef typename _Alloc::template rebind<_Tp>::other other; };
从上可以看出rebind来自_Alloc类,在上一篇中说明了_Alloc对应allocator类型有多种,这里以std::allocator为例,看ext/new_allocator.h中rebind定义。
template<typename _Tp>
class new_allocator
{
public:
template<typename _Tp1>
struct rebind
{ typedef new_allocator<_Tp1> other; };
这里可以看出rebind是重新将new_allocator(std::allocator)分配的类型绑定为_Tp1类型。但这样有什么用,std::allocator<_Tp>::rebind<_Tp1>不是等同于std::allocator<_Tp1>吗?直接用std::allocator<_Tp1>不就行吗?
为什么呢?
2.为什么定义rebind
继续往下看vector的实现,vector继承自_Vector_base
template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class vector : protected _Vector_base<_Tp, _Alloc>
{
typedef typename _Alloc::value_type _Alloc_value_type;
__glibcxx_class_requires(_Tp, _SGIAssignableConcept)
__glibcxx_class_requires2(_Tp, _Alloc_value_type, _SameTypeConcept)
typedef _Vector_base<_Tp, _Alloc> _Base;
typedef typename _Base::_Tp_alloc_type _Tp_alloc_type;
typedef __gnu_cxx::__alloc_traits<_Tp_alloc_type> _Alloc_traits;
类vector模板默认指定的分配器是std::allocator,分配的类型为_Tp,模板参数传递给_Vector_base分配类型也是_Tp。 因为_Vector_base模板中rebind的类型与模板参数相同,所以最终rebind的类型也是_Tp。这个rebind的类型没有改变过,rebind好像没什么意义啊!怎么回事?
带这个疑问去查看list容器的实现:
template<typename _Tp, typename _Alloc>
//_List_base类
class _List_base
{
protected:
typedef typename _Alloc::template rebind<_List_node<_Tp> >::other
_Node_alloc_type;
typedef typename _Alloc::template rebind<_Tp>::other _Tp_alloc_type;
// list类:
template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class list : protected _List_base<_Tp, _Alloc>
{
// concept requirements
typedef typename _Alloc::value_type _Alloc_value_type;
__glibcxx_class_requires(_Tp, _SGIAssignableConcept)
__glibcxx_class_requires2(_Tp, _Alloc_value_type, _SameTypeConcept)
typedef _List_base<_Tp, _Alloc> _Base;
typedef typename _Base::_Tp_alloc_type _Tp_alloc_type;
typedef typename _Base::_Node_alloc_type _Node_alloc_type;
list类同类vector模板默认指定的分配器是std::allocator,分配的类型为_Tp,模板参数传递给_Vector_base分配类型也是_Tp。
在_List_base类中,要为list节点分配空间,list节点是一个<_List_node<_Tp>类型数据结构,使用_Tp去分配无法正确分配。
如何分配这个数据结构呢?
ok,类型不是<_List_node<_Tp>吗,使用std::allocator<_List_node<_Tp>不是就解决了吗?
但是模板指定的分配器是std::allocator<_Tp>,怎么办?
那就增加一个模板参数用于指定分配<_List_node<_Tp>啊,ok这样好像可以哎。这样有两个问题:多了一个模板参数,两个alloc模板参数指定的分配器要确保一致。
更好的方法是只指定一个分配器,就实现两种类型的分配。
rebind就是这样的一个作用,可以直接指定另一种类型的分配。 _Alloc::template rebind<_List_node<_Tp> >::other指定分配类型为<_List_node<_Tp> 。
3.总结
综上,rebind并不是没有作用,只是在vector中不需要用到第二种类型的分配。当容器需要对不同类型使用同一分配策略时,rebind的意义就体现出来。
如:有一个容器类MyVector, 它用的是Allocator_A内存分配器,这个容器类很有可能需要double类型的分配器,而且要求对int和double类型的内存分配策略是一样的。
总之,rebind可以重新指定分配器分配空间的类型,实现了对不同类型使用同一种内存分配策略的要求。