当我们有模板模板参数时,为什么需要allocator :: rebind?

时间:2012-09-11 03:31:41

标签: c++ templates allocator template-templates

每个allocator类必须具有类似于以下内容的接口:

template<class T>
class allocator
{
    ...
    template<class Other>
    struct rebind { typedef allocator<Other> other; };
};

使用分配器的类做了多余的事情:

template<class T, class Alloc = std::allocator<T> >
class vector { ... };

但为什么这有必要呢?

换句话说,他们不能刚才说:

template<class T>
class allocator { ... };

template<class T, template<class> class Alloc = std::allocator>
class vector { ... };

哪个更优雅,更少冗余,(在某些类似的情况下)可能更安全? 为什么他们走rebind路线,这也导致更多的冗余(即你必须说T两次)?

(类似的问题转到char_traits,其余的......虽然它们都没有rebind,但它们仍然可以从模板模板参数中受益。)


编辑:

  

但是如果您需要多于1个模板参数,这将不起作用!

实际上,效果非常好!

template<unsigned int PoolSize>
struct pool
{
    template<class T>
    struct allocator
    {
        T pool[PoolSize];

        ...
    };
};

现在,如果vector仅以这种方式定义:

template<class T, template<class> class Alloc>
class vector { ... };

然后你可以说:

typedef vector<int, pool<1>::allocator> int_vector;

它会很好地工作,没有需要你(冗余地)说int两次。

rebind内的vector操作只会变为Alloc<Other>而不是Alloc::template rebind<Other>::other

4 个答案:

答案 0 :(得分:18)

来自 Foundations of Algorithms in C++11 的引用文字,第1卷,第4章,p。 35:

template <typename T> 
struct allocator 
{  
   template <typename U>  
   using  rebind = allocator<U>; 
}; 

样本用法:

allocator<int>::rebind<char> x;

C ++编程语言,第4版,第34.4.1节,p。 998,在默认分配器类中评论'经典'重新绑定成员:

template<typename U>
     struct rebind { using other = allocator<U>;};

Bjarne Stroustrup写道:

  

好奇的重新绑定模板是一个古老的别名。应该是:

template<typename U>
using other = allocator<U>;
     

但是,在C ++支持这些别名之前定义了分配器。

答案 1 :(得分:10)

  

但为什么这有必要呢?

如果你的allocator类有多个模板参数怎么办?

这就是为什么通常不鼓励使用模板模板参数,而不是使用普通模板参数,即使它意味着在实例化站点有点冗余。在许多情况下(但可能不适用于分配器),该参数可能并不总是类模板(例如,具有模板成员函数的普通类)。

您可能会发现使用模板模板参数很方便(在容器类的实现中),因为它简化了一些内部语法。但是,如果用户有一个多参数类模板作为他想要使用的分配器,但是你需要用户提供一个单参数类模板的分配器,你实际上会迫使他为几乎创建一个包装器。他必须使用该分配器的任何新环境。这不仅不可扩展,它也可能变得非常不方便。而且,在这一点上,该解决方案远不是您最初认为的“优雅且不那么多余”的解决方案。假设您有一个带有两个参数的分配器,以下哪个对用户来说最简单?

std::vector<T, my_allocator<T,Arg2> > v1;

std::vector<T, my_allocator_wrapper<Arg2>::template type > v2;

你基本上强迫用户构建很多无用的东西(包装器,模板别名等)只是为了满足你的实现需求。要求自定义分配器类的作者提供嵌套的重新绑定模板(这只是一个简单的模板别名)比使用替代方法所需的所有扭曲要容易得多。

答案 2 :(得分:4)

在您的方法中,您强制分配器成为具有单个参数的模板,而这可能并非总是如此。在许多情况下,分配器可以是非模板的,嵌套的rebind可以返回相同类型的分配器。在其他情况下,分配器可以有额外的模板参数。第二种情况是std::allocator<>的情况,因为只要实现提供了默认值,标准库中的所有模板都允许有额外的模板参数。另请注意,rebind的存在在某些情况下是可选的,其中allocator_traits可用于获取反弹类型。

标准实际上提到嵌套的rebind实际上只是一个模板化的typedef:

  

§17.6.3.5/ 3    注意A:上表中的成员类模板重新绑定是   实际上是一个typedef模板。 [注意:一般来说,如果名称   然后,分配器绑定到SomeAllocator<T>   Allocator::rebind<U>::otherSomeAllocator<U>的类型相同,   其中someAllocator<T>::value_type为T且SomeAllocator<U>::value_type为U. - 尾注]如果Allocator是类模板   形式SomeAllocator<T, Args>的实例化,其中Args为零   或更多类型参数,并且Allocator不提供重新绑定成员   模板,标准allocator_traits模板默认使用SomeAllocator<U, Args>代替Allocator:: rebind<U>::other。对于   不是上述模板实例的分配器类型   表格,没有提供默认值。

答案 3 :(得分:0)

假设您想要编写一个采用各种向量的函数。

然后能够写

更方便
template <class T, class A>
void f (std::vector <T, A> vec) {
   // ...
}

比必须写

template <class T, template <class> class A>
void f (std::vector <T, A> vec) {
   // ...
}

在大多数情况下,这样的功能无论如何都不关心分配器。

进一步注意,分配器不需要是模板。您可以为需要分配的特定类型编写单独的类。

设计分配器的更方便的方法可能是

struct MyAllocator { 
   template <class T>
   class Core {
      // allocator code here
   };
};

然后就可以写

std::vector <int, MyAllocator> vec;

而不是有些误导的表达

std::vector <int, MyAllocator<int> > vec;

我不确定在添加MyAllocator之后是否允许将上述rebind用作分配器,即以下是否是有效的分配器类:

struct MyAllocator { 
   template <class T>
   class Core {
      // allocator code here
   };

   template <class T>
   struct rebind { using other=Core<T>; };
};