【问题标题】:STL Container: Constructor's Allocator parameter and scoped allocatorsSTL Container:构造函数的分配器参数和作用域分配器
【发布时间】:2012-09-15 10:01:02
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: c++ stl c++11 containers allocator


    【解决方案1】:

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

    始终向构造函数提供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 本身,因此使用相同的分配器 容器和容器内每个元素的资源,以及 如果元素本身是容器,则它们的每个元素 递归地。如果用多个分配器实例化,则第一个 allocator 是容器使用的外部分配器,第二个 分配器被传递给容器元素的构造函数, 并且,如果元素本身是容器,则第三个分配器是 传递给元素的元素,依此类推。如果容器是嵌套的 深度大于分配器的数量,最后一个分配器 重复使用,如在单分配器的情况下,对于任何剩余的 递归。 [注意scoped_allocator_adaptor 源自 外部分配器类型,因此可以替换外部分配器 输入大多数表达式。 — 尾注 ]

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

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

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

    例如,分配器可以提供一个名为construct的成员,它将使用给定的参数在某个地址构造一个类型:

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

    如果分配器不提供此成员,allocator_traits 将提供默认实现。无论如何,容器必须使用这个construct函数构造所有value_types,但是通过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 时,您可以免费获得作用域分配器行为。

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

        猜你喜欢
        • 2017-06-14
        • 1970-01-01
        • 1970-01-01
        • 2023-01-16
        • 1970-01-01
        • 1970-01-01
        • 2013-05-23
        • 1970-01-01
        • 2011-02-05
        相关资源
        最近更新 更多