2012-09-23 81 views
14

STL容器有一个用于选择自定义分配器的模板参数。这花了一段时间,但我想我明白它是如何工作的。不知何故,它不是很好,因为给定的分配器类型不是直接使用,而是反弹到另一种类型的分配器。最后我可以使用它。STL容器:构造函数的分配器参数和作用域分配器

读完API之后,我意识到也有可能将分配器作为构造器参数。但是,我怎么知道容器使用哪种分配器,如果它从模板参数内部重新绑定给定的分配器?

此外,我读了C++ 11现在使用范围分配器,它允许重复使用容器的分配器来容纳其容器。范围化分配器启用容器的实现与不知道范围化容器的实现有何区别?

不幸的是,我无法找到任何可以解释这一点的东西。感谢您的回答!

回答

13

但是我怎么知道容器使用哪种分配的,如果它 内部从重新绑定模板参数给定的分配?

始终提供一个Allocator<T>给构造(其中T是容器的value_type)。该容器将它转换为Allocator<U>是必要的,其中U是容器的一些内部数据结构。所述Allocator需要提供这样的转换构造,例如:

template <class T> class allocator { 
    ... 
    template <class U> allocator(const allocator<U>&); 

此外我读C++ 11现在使用范围的分配器,其允许 重用容器的分配器为包含它的容器。

好,更精确地说,C++ 11有一个名为scoped_allocator_adaptor分配器适配器

template <class OuterAlloc, class... InnerAllocs> 
class scoped_allocator_adaptor : public OuterAlloc 
{ 
    ... 
}; 

从C++ 11:

类模板scoped_allocator_adaptor是一个分配器模板 ,它指定一个容器使用的内存资源(外部分配器)(与任何其他分配器一样)并指定一个内部的 分配器资源被传递给容器内的每个元素 的构造器。该适配器实例化为一个外部和零个或多个内部分配器类型。如果只有一个 分配器类型实例化,所述内分配器成为 scoped_allocator_adaptor本身,从而使用用于容器相同的分配器 资源和所述容器内的每个元件和,如果 元素本身是CON组tainers,每个它们的元素递归地为 。如果使用多个分配器实例化,则第一个分配器是容器使用的外部分配器,第二个分配器将被传递给容器元素的构造函数 ,如果元素本身是容器,则第三个分配器是 传递给元素的元素,依此类推。如果将容器嵌套 的深度大于分配器的数量,则最后一个分配器 会在单分配器情况下重复使用,对于任何剩余的 递归。 [注意scoped_allocator_adaptor源自 外部分配器类型,所以它可以代替大多数表达式中的外部分配器 类型。 - 注完]

所以,只有当您指定一个scoped_allocator_adaptor作为分配器为您的容器得到作用域分配器行为。

作用域分配器启用容器 的实现如何与不知道作用域容器的实现大致不同?

关键是容器现在通过一个名为allocator_traits的新类来处理它的分配器,而不是直接处理分配器。而容器必须使用allocator_traits进行某些操作,例如在容器中构建和销毁value_type。容器不得直接与分配器通信。

例如,分配器可以提供一种构件称为construct将使用给定的参数在一定的地址构造类型:

template <class T> class Allocator { 
    ... 
    template<class U, class... Args> 
     void construct(U* p, Args&&... args); 
}; 

如果分配器不提供此构件,allocator_traits将提供一个默认实现。在任何情况下,容器必须构建使用所有value_type s此construct功能,但使用它通过allocator_traits,而不是使用allocator直接:

allocator_traits<allocator_type>::construct(the_allocator, *ugly details*); 

scoped_allocator_adaptor定制提供construct功能,这allocator_traits将转发到利用uses_allocator特性并将正确的分配器传递给value_type构造函数。容器仍然对这些细节无知。容器只需知道它必须使用allocator_traits construct函数构造value_type

容器必须处理的更多细节才能正确处理有状态的分配器。虽然这些细节也通过让容器不作任何假设来处理,但通过allocator_traits获得所有属性和行为。该容器甚至不能假定pointerT*。而是通过询问allocator_traits发现它是什么。

简而言之,要构建一个C++ 11容器,请研究allocator_traits。然后,当客户使用scoped_allocator_adaptor时,您可以免费获得有限范围的分配器行为。

4

容器使用的分配器的类型由其构造函数参数定义:它恰好是容器的构造函数中预期的这种类型。但是,任何分配器都需要能够提供不同于它所定义的类型的类型。例如,对于std::list<T, A>,分配器预期能够分配T对象,但它永远不会用于分配这些对象,因为std::list<T, A>实际上需要分配节点。也就是说,分配器将被反弹以分配不同的类型。不幸的是,这使得使用分配器来服务特定类型变得很困难:您不知道分配器实际将提供的类型。

对于作用域分配器,它的工作非常简单:容器确定它是否具有构造函数采用匹配的分配器的任何成员。如果是这种情况,它将重新绑定它使用的分配器并将此分配器传递给成员。不直截了当的是确定是否正在使用分配器的逻辑。为了确定一个成员是否使用分配器,使用std::uses_allocator<T, A>的特征:它确定T是否具有嵌套的typedef allocator_type哪些以及如果A可以转换为这种类型。 20.6.7.2 [allocator.uses.construction]中描述了构造成员对象的规则。

实际上,这意味着分配器对于处理容器及其成员使用的池很有用。在某些情况下,当分配类似大小的对象时,它也可能合理。对于任何基于节点的容器,都要保留一个相同大小的对象池。但是,从与分配器一起使用的模式(如节点或包含的某些字符串)来看,没有必要清楚。此外,由于使用不同的分配策略会改变类型,因此似乎最合理的是坚持默认分配或使用分配器类型,该分配器类型是实际定义分配策略的多态分配器的代理。当然,当你拥有有状态的分配器时,你可能拥有不同分配器的对象,例如swap()他们可能无法正常工作。