在C ++ 11标准中,我们在动态内存管理库中有std::scoped_allocator_adaptor
。这个班级最重要的用例是什么?
答案 0 :(得分:34)
如果你想要一个字符串容器,并希望对容器及其元素使用相同的分配器(因此它们都被分配在同一个领域,如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_type
是scoped_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_type
是scoped_allocator_adaptor
的另一个特化,所以如果元素类型也是一个容器,它使用相同的协议来构造其元素,并且分配器可以传递下来每个元素,即使你有容器容器等容器。
因此,适配器的目的是包装现有的分配器并执行构造函数参数的所有元编程和操作,以将分配器从容器传播到其子代。
答案 1 :(得分:4)
假设你有一个有状态的竞技场分配器Alloc
和一个构造函数Alloc(Arena&)
,它允许你的应用程序有一些特殊的性能,并说你使用这样的容器的嵌套层次结构:
using InnerCont = std::vector<int, Alloc<int>>;
using OuterCont = std::vector<InnerCont, std::scoped_allocator_adaptor<Alloc<InnerCont>>>;
在这里,使用scoped_allocator_adaptor
将允许您将用于初始化分配器的竞技场对象从外部传播到内部容器,如下所示:
auto my_cont = OuterCont{std::scoped_allocator_adaptor(Alloc<InnerCont>{my_arena})};
这可以实现更大的数据位置,并允许您为整个容器层次结构预分配一个大内存竞技场my_arena
,而不是仅为外部容器提供my_arena
并且需要在所有内容容器上循环,并为该级别的每个元素提供另一个竞技场。
类模板实际上是一个可变参数模板,它可以精确控制在每种类型的容器层次结构中使用哪种类型的分配器。据推测,这给复杂的数据结构提供了更好的性能(我必须承认,我似乎在不同级别的行动中看起来不同的分配器,但也许有数百万用户的大型数据中心在这里有一个用例)。