polymorphic_allocator: когда и зачем мне его использовать?

123

Вот документация по cppreference , вот рабочий проект.

Должен признать, что я не понимал, какова настоящая цель polymorphic_allocatorи когда / почему / как я должен его использовать.
Например, pmr::vectorподпись имеет следующую подпись:

namespace pmr {
    template <class T>
    using vector = std::vector<T, polymorphic_allocator<T>>;
}

Что polymorphic_allocatorпредлагает? Что std::pmr::vectorпредлагает предложение относительно старомодного std::vector? Что я могу сделать сейчас, чего не мог сделать до сих пор?
Какова реальная цель этого распределителя и когда я должен его использовать?

скайпджек
источник
1
Они пытаются преодолеть некоторые проблемы по allocator<T>своей сути. Таким образом, вы увидите в этом ценность, если будете часто использовать распределители.
edmz
2
Соответствующий документ .
edmz

Ответы:

103

Выборочная цитата из cppreference:

Этот полиморфизм времени выполнения позволяет объектам, использующим polymorphic_allocator, вести себя так, как если бы они использовали разные типы распределителей во время выполнения, несмотря на идентичный тип статического распределителя.

Проблема с «обычными» распределителями в том, что они меняют тип контейнера. Если вам нужен vectorконкретный распределитель, вы можете использовать Allocatorпараметр шаблона:

auto my_vector = std::vector<int,my_allocator>();

Проблема в том, что этот вектор не того же типа, что и вектор с другим распределителем. Вы не можете передать его функции, которая требует, например, вектора распределителя по умолчанию, или назначить два вектора с другим типом распределителя одной и той же переменной / указателю, например:

auto my_vector = std::vector<int,my_allocator>();
auto my_vector2 = std::vector<int,other_allocator>();
auto vec = my_vector; // ok
vec = my_vector2; // error

Полиморфный распределитель - это отдельный тип распределителя с членом, который может определять поведение распределителя через динамическую отправку, а не через механизм шаблона. Это позволяет вам иметь контейнеры, которые используют конкретное настраиваемое распределение, но все же имеют общий тип.

Настройка поведения распределителя выполняется путем предоставления распределителю std::memory_resource *:

// define allocation behaviour via a custom "memory_resource"
class my_memory_resource : public std::pmr::memory_resource { ... };
my_memory_resource mem_res;
auto my_vector = std::pmr::vector<int>(0, &mem_res);

// define a second memory resource
class other_memory_resource : public std::pmr::memory_resource { ... };
other_memory_resource mem_res_other;
auto my_other_vector = std::pmr::vector<int>(0, &mes_res_other);

auto vec = my_vector; // type is std::pmr::vector<int>
vec = my_other_vector; // this is ok -
      // my_vector and my_other_vector have same type

На мой взгляд, основная оставшаяся проблема заключается в том, что std::pmr::контейнер все еще несовместим с эквивалентным std::контейнером, использующим распределитель по умолчанию. При разработке интерфейса, работающего с контейнером, вам нужно принять некоторые решения:

  • Возможно ли, что переданный контейнер потребует особого распределения?
  • если да, следует ли мне добавить параметр шаблона (чтобы разрешить использование произвольных распределителей) или мне следует обязать использование полиморфного распределителя?

Шаблонное решение позволяет использовать любой распределитель, включая полиморфный распределитель, но имеет другие недостатки (размер сгенерированного кода, время компиляции, код должен быть представлен в файле заголовка, возможность дальнейшего «загрязнения типов», которое продолжает выталкивать проблему наружу). Полиморфный раствор Распределитель с другой стороны , диктует , что полиморфная Распределитель должны быть использованы. Это исключает использование std::контейнеров, которые используют распределитель по умолчанию, и может иметь последствия для взаимодействия с устаревшим кодом.

По сравнению с обычным распределителем, полиморфный распределитель имеет некоторые незначительные затраты, такие как накладные расходы на хранилище указателя memory_resource (что, скорее всего, незначительно) и стоимость диспетчеризации виртуальных функций для распределений. Основная проблема, вероятно, заключается в отсутствии совместимости с устаревшим кодом, который не использует полиморфные распределители памяти.

Давмак
источник
2
Итак, std::pmr::очень ли вероятно , что двоичный макет для классов будет другим?
Эури Пинхоллоу
12
@EuriPinhollow вы не можете reinterpret_castмежду a std::vector<X>и std::pmr::vector<X>, если это то, о чем вы спрашиваете.
davmac
4
В простых случаях, когда ресурс памяти не зависит от переменной времени выполнения, хороший компилятор будет девиртуализировать, и вы получите полиморфный распределитель без дополнительных затрат (за исключением хранения указателя, что на самом деле не проблема). Я подумал, что об этом стоит упомянуть.
DeiDei
1
@ Yakk-AdamNevraumont « std::pmr::контейнер все еще несовместим с эквивалентным std::контейнером, использующим распределитель по умолчанию» . Также нет оператора присваивания, определяемого от одного к другому. Если сомневаетесь, попробуйте: godbolt.org/z/Q5BKev (код отличается от приведенного выше, потому что gcc / clang имеет полиморфные классы распределения в «экспериментальном» пространстве имен).
davmac
1
@davmac А, значит, template<class OtherA, std::enable_if< A can be constructed from OtherA > vector( vector<T, OtherA>&& )конструктора нет. Я был неуверен и не знал, где найти компилятор с PMR, совместимым с TS.
Якк - Адам Неврамонт
33

polymorphic_allocatorдля пользовательского распределителя, как std::functionдля прямого вызова функции.

Он просто позволяет вам использовать распределитель с вашим контейнером, не решая в момент объявления, какой именно. Поэтому, если у вас есть ситуация, когда подходит более одного распределителя, вы можете использовать polymorphic_allocator.

Возможно, вы хотите скрыть, какой распределитель используется для упрощения вашего интерфейса, или, может быть, вы хотите иметь возможность заменить его для разных случаев выполнения.

Сначала вам нужен код, которому нужен распределитель, затем вам нужно иметь возможность поменять местами, какой из них используется, прежде чем рассматривать вектор pmr.

Якк - Адам Неврамонт
источник
7

Один из недостатков полиморфных распределителей - это polymorphic_allocator<T>::pointerвсегда справедливо T*. Это означает, что вы не можете использовать их с причудливыми указателями . Если вы хотите сделать что-то вроде размещения элементов a vectorв разделяемой памяти и доступа к ним через boost::interprocess::offset_ptrs , вам нужно использовать для этого обычный старый неполиморфный распределитель.

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

Макс.
источник
2
Это ключевой момент и большой облом. Статья Артура О'Двайера « На пути к осмысленным модным указателям» исследует территорию, как и его книга «Освоение c ++ 17 STL»
см.
Можете ли вы дать реальный пример использования полиморфного распределителя?
darune