STL容器有一个模板参数来选择自定义分配器。花了一段时间,但我想我明白它是如何工作的。不知何故,它不是很好,因为给定的分配器类型不是直接使用,而是反弹到另一种类型的分配器。最后我可以使用它。
在阅读API后,我认识到还有可能将allocators作为构造函数参数。但是,我如何知道容器使用哪种分配器,如果它在内部从模板参数重新绑定给定的分配器?
此外,我读到C ++ 11现在使用范围分配器,它允许重用容器的分配器来容纳容器。如何启用作用域分配器容器的实现与不知道作用域容器的容器大致不同?
不幸的是我无法找到任何可以解释这一点的东西。谢谢你的回答!
答案 0 :(得分: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
是一个分配器模板 指定要使用的内存资源(外部分配器) 一个容器(与任何其他分配器一样)并且还指定一个内部 要传递给每个元素的构造函数的allocator资源 在容器内。此适配器实例化为一个外部和 零个或多个内部分配器类型。如果只用一个实例化 分配器类型,内部分配器成为scoped_allocator_adaptor
本身,因此使用相同的分配器 容器的资源和容器中的每个元素, 如果元素本身是容器,它们的每个元素 递归。如果用多个分配器实例化,则第一个 allocator是容器使用的外部分配器,第二个 allocator传递给容器元素的构造函数, 并且,如果元素本身是容器,则第三个分配器是 传递给元素的元素,依此类推。如果容器是嵌套的 深度大于分配器的数量,最后一个分配器 对于任何剩余的,在单分配器的情况下重复使用 递归。 [注意: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
函数构建所有construct
,但通过allocator_traits
使用它,而不使用allocator
直接:
allocator_traits<allocator_type>::construct(the_allocator, *ugly details*);
scoped_allocator_adaptor
提供construct
个自定义allocator_traits
函数,uses_allocator
将转发利用value_type
特征的函数,并将正确的分配器传递给value_type
构造函数。容器仍然对这些细节一无所知。容器只需要知道它必须使用allocator_traits construct
函数构建allocator_traits
。
容器必须具有更多细节才能正确处理有状态分配器。虽然这些细节也是通过让容器不做任何假设但通过pointer
获得所有属性和行为来处理的。容器甚至不能假设T*
是allocator_traits
。而是通过询问allocator_traits
它是什么来找到这种类型。
简而言之,要构建一个C ++ 11容器,请研究scoped_allocator_adaptor
。然后,当您的客户使用{{1}}时,您可以免费获得范围内的分配器行为。
答案 1 :(得分: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()
他们可能无法正常工作。