【问题标题】:What is the purpose of std::scoped_allocator_adaptor?std::scoped_allocator_adaptor 的目的是什么?
【发布时间】:2014-04-04 14:08:44
【问题描述】:

在 C++11 标准中,我们在动态内存管理库中有 std::scoped_allocator_adaptor。这个类最重要的用例是什么?

【问题讨论】:

  • @dyp 不,还没有。我会读一读。
  • @dyp 的 URL 末尾有一些奇怪的编码 - http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2554.pdf%E2%80%8E - 试试 this one instead。 (%E2%80%8E 是 URL 编码的 UTF-8 left-to-right-mark。)
  • @Casey 谢谢,删除我的评论。从谷歌搜索中复制了 URL ;)

标签: c++ memory-management c++11 containers allocator


【解决方案1】:

如果您想要一个字符串容器并希望对容器及其元素使用相同的分配器(因此它们都被分配在同一个区域中,正如 TemplateRex 所描述的那样),那么您可以手动执行此操作:

template<typename T>
  using Allocator = SomeFancyAllocator<T>;
using String = std::basic_string<char, std::char_traits<char>, Allocator<char>>;
using Vector = std::vector<String, Allocator<String>>;

Allocator<String> as( some_memory_resource );
Allocator<char> ac(as);
Vector v(as);
v.push_back( String("hello", ac) );
v.push_back( String("world", ac) );

但是,这很尴尬且容易出错,因为很容易意外插入不使用相同分配器的字符串:

v.push_back( String("oops, not using same memory resource") );

std::scoped_allocator_adaptor 的目的是自动将分配器传播到它构造的对象如果它们支持使用分配器进行构造。所以上面的代码会变成:

template<typename T>
  using Allocator = SomeFancyAllocator<T>;
using String = std::basic_string<char, std::char_traits<char>, Allocator<char>>;
using Vector = std::vector<String, std::scoped_allocator_adaptor<Allocator<String>>>;
                                   /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
Allocator<String> as( some_memory_resource );
Allocator<char> ac(as);
Vector v(as);
v.push_back( String("hello") );  // no allocator argument needed!
v.push_back( String("world") );  // no allocator argument needed!

现在向量的分配器自动用于构造其元素,即使插入的对象String("hello")String("world") 不是使用相同的分配器构造的。由于basic_string 可以从const char* 隐式构造,所以最后两行可以进一步简化:

v.push_back( "hello" );
v.push_back( "world" );

由于scoped_allocator_adaptor 自动使用向量的分配器构造元素,这更加简单、易读且不易出错。

当向量要求其分配器构造一个元素作为obj 的副本时,它会调用:

std::allocator_traits<allocator_type>::construct( get_allocator(), void_ptr, obj );

通常分配器的construct() 成员会调用类似:

::new (void_ptr) value_type(obj);

但是如果allocator_typescoped_allocator_adaptor&lt;A&gt;,那么它使用模板元编程来检测value_type 是否可以使用适配类型的分配器来构造。如果value_type 在其构造函数中不使用分配器,那么适配器会:

std::allocator_traits<outer_allocator_type>::construct(outer_allocator(), void_ptr, obj);

这将调用嵌套分配器的construct() 成员,它使用类似placement new 的东西,如上所述。但是如果对象确实支持在其构造函数中使用分配器,那么scoped_allocator_adaptor&lt;A&gt;::construct() 可以:

std::allocator_traits<outer_allocator_type>::construct(outer_allocator(), void_ptr, obj, inner_allocator());

或:

std::allocator_traits<outer_allocator_type>::construct(outer_allocator(), void_ptr, std::allocator_arg, inner_allocator(), obj);

即适配器在其嵌套分配器上调用construct() 时传递附加参数,以便将使用分配器构造对象。 inner_allocator_typescoped_allocator_adaptor 的另一个特化,所以如果元素类型也是一个容器,它使用相同的协议来构造 its 元素,并且分配器可以传递给每个元素,即使你有容器容器等容器。

因此,适配器的目的是包装现有分配器并执行所有元编程和构造函数参数的操作,以将分配器从容器传播到其子级。

【讨论】:

  • 向量分配器(std::scoped_allocator_adaptor>)是否需要明确告知其内部分配器是 Allocator?这个解释很有帮助,但它似乎没有涵盖 scoped_allocator_adaptor 的内部分配器模板参数的使用。我希望看到“std::scoped_allocator_adaptor, Allocator>”。我只是糊涂了吗?
  • ...哦,我想我明白了...范围内的分配器可以准确地告诉内部分配器类型是什么,但是由于 Allocator 和 Allocator 密切相关,甚至可以相互构造(显然),将 Allocator 实例提供给 string() 并依靠隐式转换从中构造所需的 Allocator 就足够了。 (除非我比我想象的更困惑。)在不同的情况下,两个级别的分配器不是那么相关,范围分配器需要通过其模板参数明确告知两者。
  • @mjwach,正确。在一般情况下,您将使用 scoped_allocator_adaptor 的可变参数模板参数列表为不同级别的嵌套元素嵌套不同的分配器
  • 自从写了这些 cmets 之后,我还了解到(来自 en.cppreference.com/w/cpp/concept/Allocator)能够将 Allocator 复制/移动/投射到 Allocator 等只是其中的一部分为了被认为是真正的分配器(如果我没看错的话),必须满足一组分配器类型的合同。所以这是我在这里缺少的主要内容,我只是对这些主题进行了零碎的教育。
  • @JonathanWakely 很好的答案。你在这里没有提到任何关于性能的事情。所以我希望分配器适配器根本不会影响分配器的性能。
【解决方案2】:

假设您有一个有状态的 arena 分配器 Alloc 和一个构造函数 Alloc(Arena&amp;),它允许您的应用程序具有一些特殊性能,并假设您使用这样的容器的嵌套层次结构:

using InnerCont = std::vector<int, Alloc<int>>;    
using OuterCont = std::vector<InnerCont, std::scoped_allocator_adaptor<Alloc<InnerCont>>>;    

在这里,scoped_allocator_adaptor 的使用将让您将用于初始化分配器的 arena 对象从外部容器传播到内部容器,如下所示:

auto my_cont = OuterCont{std::scoped_allocator_adaptor(Alloc<InnerCont>{my_arena})};

这实现了更大的数据局部性,并允许您为整个容器层次结构预先分配一个大内存区域 my_arena,而不是仅使 my_arena 可用于外部容器,并且需要循环遍历所有内部容器,并为该级别的每个元素使用另一个竞技场。

类模板实际上是一个可变参数模板,它让您可以细粒度地控制在每种容器层次结构中使用哪种类型的分配器。大概这会给复杂的数据结构带来更好的性能(我必须承认,我在任何地方似乎都没有不同级别的不同分配器,但也许拥有数百万用户的大型数据中心在这里有一个用例)。

【讨论】:

    猜你喜欢
    • 2011-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-20
    • 1970-01-01
    • 1970-01-01
    • 2015-01-20
    • 2020-03-19
    相关资源
    最近更新 更多