std::scoped_allocater_aptor的用途是什么

What is the purpose of std::scoped_allocator_adaptor?

本文关键字:是什么 allocater scoped std aptor      更新时间:2023-10-16

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

如果您想要一个字符串容器,并希望对容器及其元素使用相同的分配器(因此,正如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<A>,则它使用模板元编程来检测value_type是否可以用适配类型的分配器来构造。如果value_type在其构造函数中不使用分配器,那么适配器会使用:

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

这将调用嵌套分配器的construct()成员,该成员使用类似于placement new的东西,如上所述。但是,如果对象确实支持在其构造函数中使用分配器,那么scoped_allocator_adaptor<A>::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的另一个特殊化,因此,如果元素类型也是一个容器,它会使用相同的协议来构造元素,并且分配器可以传递到每个元素,即使您有容器的容器等。

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

假设您有一个带构造函数Alloc(Arena&)的有状态竞技场分配器Alloc,它允许您的应用程序具有一些特殊性能,并假设您使用一个嵌套的容器层次结构,如下所示:

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可用于外部容器,并且需要在所有内部容器上循环,该级别的每个元素都有另一个区域。

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