【问题标题】:Why is allocator::rebind necessary when we have template template parameters?当我们有模板模板参数时,为什么需要分配器::重新绑定?
【发布时间】:2012-09-03 22:47:39
【问题描述】:

每个分配器类都必须有一个类似于以下的接口:

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

vector 中的rebind 操作将变成Alloc&lt;Other&gt; 而不是Alloc::template rebind&lt;Other&gt;::other

【问题讨论】:

  • 请注意,在 C++11 中,要求放宽了,如果 SomeAllocator 不提供 rebind,则 std::allocator_traits&lt;SomeAllocator&lt;T, Args...&gt;&gt;::rebind_alloc&lt;U&gt;SomeAllocator&lt;U, Args...&gt; 作为合理的默认值。
  • 到编辑的最后一点:重新绑定操作在向量实现中看起来有多丑是无关紧要的。你,实施者,有责任让用户的事情变得简单,即使这意味着非常丑陋和复杂的代码。如果您可以将丑陋隐藏在实现中以留下更干净的界面,那么您的工作就是这样做。
  • @MikaelPersson:当然,但是对用户来说更容易吗? (怎么会?示例/比较会有所帮助!:D)
  • 真相可能令人失望。模板重新绑定习惯用旧编译器可能更容易实现。我发现模板模板参数只在较新的 STL 代码中传递。所以并不是说实现者一般不喜欢模板模板参数。我个人喜欢模板模板参数的一点是,仅在句法分析之后,特定意图在接口级别就已经可见,即传递一种 strategy 供内部私有通用使用。
  • 如果pool&lt;1&gt;::allocator&lt;char&gt;::rebind&lt;int&gt;::other 需要是pool&lt;4&gt;::allocator&lt;int&gt;

标签: c++ templates allocator template-templates


【解决方案1】:

引用自 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 引用 stroustrup,顺便说一下您的链接已损坏
【解决方案2】:

但是为什么这是必要的呢?

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

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

您可能会发现(在容器类的实现中)使用模板模板参数很方便,因为它简化了一些内部语法。但是,如果用户有一个多参数类模板作为他想要使用的分配器,但您要求用户提供一个分配器,它是一个单参数类模板,您实际上将强制他为几乎他必须使用该分配器的任何新上下文。这不仅不可扩展,而且做起来也很不方便。而且,在这一点上,该解决方案远不是您最初认为的“优雅且较少冗余”的解决方案。假设您有一个带有两个参数的分配器,以下哪个对用户来说最简单?

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

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

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

【讨论】:

  • “如果您的分配器类有多个模板参数怎么办?” template&lt;class P1, class P2&gt; struct my_allocator_with_args { template&lt;class T&gt; my_allocator { ... }; };... 现在您只需将 my_allocator_with_args&lt;Foo, Bar&gt;::my_allocator 作为模板模板参数传递.
  • 顺便说一句,这个答案是大卫的答案的欺骗。 (这也是别人的答案的欺骗,但他很快就把它删除了。)
  • 谢谢,现在查看我的编辑。 ;) 如果您仍然认为这是一个更糟糕的选择,您能否编辑您的答案以包含“不可扩展”的情况,以及当我们使用 rebind 时等效的“可扩展”版本是什么?我认为我们中的一个人缺少一些东西。 :)
  • 顺便说一句,在你完成一个例子之后;)还有另一个问题:如果这不是模板模板参数想要解决的问题,那么它们的目的是什么? ?
【解决方案3】:

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

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

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

【讨论】:

  • 据我所见,模板参数的数量不可能成为问题——如果你需要不同数量的参数,那么你可以只需要一个参数的包装器,这比拥有rebind 更简单、更安全/更少冗余,对吧?
  • @Mehrdad:你需要创建 a 包装器,实际上更像 N 包装器,可能有很多,一个用于程序中使用的所有其他参数的每个可能组合,其中在每个 wrapper 中,所有其他参数将 绑定 到某个值
  • 我不明白你为什么说N...你能详细说明/举个例子吗?
  • @Mehrdad:给定一个模板化的分配器,它接受两个参数 TArg,如何为 @987654338 的所有可能值创建一个带有参数 T 的单个参数包装器@?
  • 呃,嵌套模板类?外层采用Arg,内层采用T。您将内部参数作为模板模板参数传递...实现与以前相同的确切目标,为您提供所需的模板参数数量,但仍按您需要的方式工作。 template&lt;class P1, class P2&gt; struct my_allocator_with_args { template&lt;class T&gt; my_allocator { ... }; };... 现在您只需将 my_allocator_with_args&lt;Foo, Bar&gt;::my_allocator 作为模板模板参数传递。我看不到 N 的来源。
【解决方案4】:

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

那么能写就方便多了

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>; };
};

【讨论】:

  • JhonB,为什么不只是using other = core&lt;T&gt; 没有结构?
猜你喜欢
  • 2018-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-23
  • 1970-01-01
  • 2011-08-28
相关资源
最近更新 更多