Подтвердить что ты не робот

Какова цель std:: scoped_allocator_adapter?

В стандарте С++ 11 мы имеем std::scoped_allocator_adapter в библиотеке управления динамической памятью. Каковы наиболее важные варианты использования этого класса?

4b9b3361

Ответ 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_type - scoped_allocator_adaptor<A>, то он использует метапрограммирование шаблонов, чтобы определить, можно ли построить value_type с помощью распределителя адаптированного типа. Если value_type не использует распределители в своих конструкторах, то адаптер выполняет:

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

И это вызовет член вложенного распределителя construct(), который использует что-то вроде размещения 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, поэтому, если тип элемента также является контейнером, он использует тот же протокол для построения своих элементов, и распределитель может передаваться каждому элементу, даже если у вас есть контейнеры контейнеров контейнеров и т.д.

Таким образом, целью адаптера является обернуть существующий распределитель и выполнить все метапрограммирование и манипулирование аргументами конструктора для распространения распределителей из контейнера в его дочерние элементы.

Ответ 2

Предположим, что у вас есть генератор арендной платы с выражением состояния 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 доступным для внешнего контейнера и потребовать петля над всеми контейнерами-контейнерами с другой ареной для каждого элемента на этом уровне.

Шаблон класса на самом деле представляет собой вариационный шаблон, который дает вам мелкомасштабный контроль над тем, какой тип распределителя использовать в каждом типе иерархии контейнеров. Предположительно, это дает более сложную структуру данных. Я должен признаться, что я не представляю разные распределители на разных уровнях в любом месте, но, возможно, крупные центры обработки данных с многомиллионными пользователями здесь имеют смысл).