14

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

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

此外,我读到 C++11 现在使用范围分配器,它允许将容器的分配器重用于其包含的容器。启用范围分配器的容器的实现与不知道范围容器的实现有何不同?

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

4

2 回答 2

13

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

始终Allocator<T>向构造函数提供一个(容器的在哪里Tvalue_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本身,因此对容器和容器中的每个元素使用相同的分配器资源,如果元素本身是容器,则递归地使用它们的每个元素。如果使用多个分配器实例化,则第一个分配器是容器使用的外部分配器,第二个分配器传递给容器元素的构造函数,如果元素本身是容器,则第三个分配器传递给容器元素的元素等等。如果容器嵌套的深度大于分配器的数量,则会重复使用最后一个分配器,就像在单分配器的情况下一样,用于任何剩余的递归。[scoped_allocator_adaptor派生自外部分配器类型,因此可以在大多数表达式中替换外部分配器类型。——尾注]

因此,如果您将 a 指定scoped_allocator_adaptor为容器的分配器,您只会获得作用域分配器行为。

启用范围分配器的容器的实现与不知道范围容器的实现有何不同?

关键是容器现在通过一个新的类allocator_traits来处理它的分配器,而不是直接处理分配器。并且容器必须用于allocator_traits某些操作,例如value_type在容器中构造和销毁s。容器不能直接与分配器对话。

例如,分配器可以提供一个被调用的成员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构造函数。容器仍然对这些细节一无所知。容器只需要知道它必须构造value_typeusingallocator_traits construct函数。

容器必须处理更多细节才能正确处理有状态分配器。尽管这些细节也是通过让容器不做任何假设而是通过allocator_traits. 容器甚至不能假设pointerT*. 而是通过询问allocator_traits它是什么来找到这种类型。

简而言之,要构建 C++11 容器,请研究allocator_traits. 然后,当您的客户使用scoped_allocator_adaptor.

于 2012-09-23T23:51:00.497 回答
4

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

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

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

于 2012-09-23T22:22:37.817 回答