Ral*_*zky 28 c++ containers memory-management allocator c++11
在C++ 11标准中,我们std::scoped_allocator_adaptor
在动态内存管理库中.这个班级最重要的用例是什么?
Jon*_*ely 37
如果你想要一个字符串容器,并希望对容器及其元素使用相同的分配器(因此它们都在同一个领域中分配,如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) );
Run Code Online (Sandbox Code Playgroud)
但是,这很容易出错并且容易出错,因为很容易意外地插入一个不使用相同分配器的字符串:
v.push_back( String("oops, not using same memory resource") );
Run Code Online (Sandbox Code Playgroud)
目的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!
Run Code Online (Sandbox Code Playgroud)
现在向量的分配器被自动用于构造它的元素,即使被插入的对象,String("hello")
并且String("world")
,不与相同分配器构造.由于basic_string
可以从const char*
最后两行隐式构造,因此可以进一步简化:
v.push_back( "hello" );
v.push_back( "world" );
Run Code Online (Sandbox Code Playgroud)
由于scoped_allocator_adaptor
使用向量的分配器自动构造元素,因此这更简单,更易于阅读,并且更不容易出错.
当向量要求其分配器构造一个元素作为obj
它的副本时调用:
std::allocator_traits<allocator_type>::construct( get_allocator(), void_ptr, obj );
Run Code Online (Sandbox Code Playgroud)
通常,分配器的construct()
成员会调用类似于:
::new (void_ptr) value_type(obj);
Run Code Online (Sandbox Code Playgroud)
但是,如果allocator_type
是,scoped_allocator_adaptor<A>
那么它使用模板元编程来检测是否value_type
可以使用自适应类型的分配器构造.如果value_type
不在其构造函数中使用allocator,那么适配器会:
std::allocator_traits<outer_allocator_type>::construct(outer_allocator(), void_ptr, obj);
Run Code Online (Sandbox Code Playgroud)
这将调用嵌套分配器的construct()
成员,它使用像placement new这样的东西,如上所述.但是如果对象支持在其构造函数中使用分配器,则执行以下scoped_allocator_adaptor<A>::construct()
操作:
std::allocator_traits<outer_allocator_type>::construct(outer_allocator(), void_ptr, obj, inner_allocator());
Run Code Online (Sandbox Code Playgroud)
要么:
std::allocator_traits<outer_allocator_type>::construct(outer_allocator(), void_ptr, std::allocator_arg, inner_allocator(), obj);
Run Code Online (Sandbox Code Playgroud)
即,适配器在调用construct()
其嵌套分配器时传递其他参数,以便使用分配器构造对象.这inner_allocator_type
是另一个特化scoped_allocator_adaptor
,所以如果元素类型也是一个容器,它使用相同的协议来构造它的元素,并且分配器可以传递给每个元素,即使你有容器容器的容器等.
因此,适配器的目的是包装现有的分配器并执行构造函数参数的所有元编程和操作,以将分配器从容器传播到其子代.
假设您有一个Alloc
带构造函数的有状态竞技场分配器,该构造函数Alloc(Arena&)
为您的应用程序提供了一些特殊的性能,并说您使用了嵌套的容器层次结构,如下所示:
using InnerCont = std::vector<int, Alloc<int>>;
using OuterCont = std::vector<InnerCont, std::scoped_allocator_adaptor<Alloc<InnerCont>>>;
Run Code Online (Sandbox Code Playgroud)
在这里,使用scoped_allocator_adaptor
will将使您将用于初始化分配器的arena对象从外部容器传播到内部容器,如下所示:
auto my_cont = OuterCont{std::scoped_allocator_adaptor(Alloc<InnerCont>{my_arena})};
Run Code Online (Sandbox Code Playgroud)
这样可以实现更大的数据局部性,并允许您my_arena
为整个容器层次结构预先分配一个大的内存区域,而不是仅对my_arena
外部容器可用,并且需要在所有内部容器上循环,并为该级别的每个元素提供另一个区域。
类模板实际上是可变参数模板,可让您对每种类型的容器层次结构中使用的分配器类型进行细粒度控制。据推测,这可以使复杂的数据结构具有更好的性能(我必须承认,我在任何地方都没有在不同级别上使用不同的分配器,但也许拥有数百万用户的大型数据中心在这里有用例)。