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

Использование распределителей в пользовательском классе контейнера

Я разрабатываю контейнерный класс, и я бы хотел использовать стандартную инфраструктуру распределителя так же, как и стандартные контейнеры. В сети я нахожу много материала о том, как использовать только класс std::allocator или как определить пользовательский распределитель для стандартных контейнеров, но материал о том, как сделать в основном использование стандартного соответствующего распределителя, очень редок, в особенно в контексте С++ 11, где вещи кажутся намного проще с точки зрения того, кто пишет пользовательский распределитель, но более сложный с точки зрения контейнера.

Итак, мой вопрос в том, как правильно использовать стандартный соответствующий распределитель наиболее общим образом, в частности:

  • Прежде всего, когда я должен создать пользовательский контейнер таким образом? Существует ли разумная служебная нагрузка (включая отсутствующие возможности оптимизации) при использовании распределителя по умолчанию вместо простого нового/удаления?
  • Должен ли я явно вызвать деструкторы содержащихся объектов?
  • Как я могу различить между агентами с сохранением состояния и безстоящими?
  • Как обрабатывать генераторы с сохранением состояния?
    • Когда (если когда-либо) два экземпляра взаимозаменяемы (когда я могу уничтожить с одним экземпляром память, выделенную с другой)?
    • Они должны быть скопированы при копировании контейнера?
    • Они могут/должны быть перемещены при перемещении контейнера?
    • В конструкторе перемещения контейнера и операторе присваивания перемещения, когда я могу переместить указатель в выделенную память и когда мне нужно выделить другую память и переместить элементы вместо этого?
  • Есть ли проблемы с безопасностью исключений в этом контексте?

Мне особенно интересен ответ о мире С++ 11 (это что-то меняет в С++ 14?)

4b9b3361

Ответ 1

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

  • Прежде всего, когда я должен создать пользовательский контейнер таким образом? Имеются ли разумные эксплуатационные издержки (в том числе отсутствует возможности оптимизации) при использовании распределителя по умолчанию вместо plain new/delete?

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

  • Должен ли я явно вызвать деструкторы содержащихся объектов?

Вы должны явно вызвать allocator_traits<allocator_type>::destroy(alloc, ptr), который, в свою очередь, будет либо напрямую вызывать деструктор value_type, либо вызовет элемент destroy allocator_type.

  • Как я могу различить между агентами с сохранением состояния и безстоящими?

Я бы не стал беспокоиться. Просто предположим, что распределитель имеет состояние.

  • Как обрабатывать распределенные ресурсы с состоянием?

Соблюдайте правила, изложенные на С++ 11 очень осторожно. Особенно те, которые предназначены для контейнеров, предназначенных для распределителей, указанных в [container.requirements.general]. Правила слишком многочисленны, чтобы перечислять здесь. Однако я с удовольствием отвечаю на конкретные вопросы по любому из этих правил. Но первый шаг - получить копию стандарта и прочитать его, по крайней мере, в разделах требований к контейнерам. Я рекомендую последний рабочий проект С++ 14 для этой цели.

  • Когда (если когда-либо) два экземпляра взаимозаменяемы (когда я могу уничтожить с одним экземпляром память, выделенную с другой)?

Если два распределителя сравниваются равными, то либо можно освободить указатели, выделенные от другого. Копии (либо путем создания копии, либо копирования) должны сравниваться с равными.

  • Они должны быть скопированы при копировании контейнера?

Найдите стандартные для propagate_on и select_on_container_copy_construction для подробных подробностей. Ответ на ореховую скорлупу "это зависит".

  • Они могут/должны быть перемещены при перемещении контейнера?

Должны быть для перемещения. Назначение перемещения зависит от propagate_on_container_move_assignment.

  • В конструкторе перемещения контейнера и операторе присваивания перемещения, когда я могу переместить указатель в выделенную память и когда мне нужно выделить другую память и вместо этого переместите элементы?

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

Оператор присваивания переходов, возможно, является самым сложным: поведение зависит от propagate_on_container_move_assignment и сравниваются ли два распределителя равными. Более полное описание приведено ниже в моем "чит-лист распределителя".

  • Есть ли проблемы с безопасностью исключений в этом контексте?

Да, тонн. [allocator.requirements] перечисляет требования распределителя, от которых может зависеть контейнер. Это включает в себя, какие операции можно и не могут выполнить.

Вам также необходимо иметь дело с тем, что распределитель pointer на самом деле не является value_type*. [allocator.requirements] также является местом для поиска этих деталей.

Удачи. Это не начинающий проект. Если у вас есть более конкретные вопросы, отправьте их в SO. Чтобы начать работу, перейдите прямо к стандарту. Я не знаю ни одного авторитетного источника по этому вопросу.

Вот чит-лист, который я сделал для себя, который описывает поведение распределителя и специальные элементы контейнера. Он написан на английском языке, а не стандартно. Если вы обнаружите какие-либо несоответствия между моим чит-листом и рабочим проектом С++ 14, доверяйте рабочему проекту. Одно из известных расхождений заключается в том, что я добавил спецификации noexcept способами, которых нет в стандарте.


поведение Allocator:

C() noexcept(is_nothrow_default_constructible<allocator_type>::value);

C(const C& c);

Получает распределитель из alloc_traits::select_on_container_copy_construction(c).

C(const C& c, const allocator_type& a);

Получает распределитель от a.

C(C&& c)
  noexcept(is_nothrow_move_constructible<allocator_type>::value && ...);

Получает распределитель из move(c.get_allocator()), передает ресурсы.

C(C&& c, const allocator_type& a);

Получает распределитель от a. Передает ресурсы, если a == c.get_allocator(). Переместите конструкты из каждого c[i], если a != c.get_allocator().

C& operator=(const C& c);

Если alloc_traits::propagate_on_container_copy_assignment::value - true, копия назначает распределители. В этом случае, если распределители не равны ранее для присваивания, выгружает все ресурсы из *this.

C& operator=(C&& c)
  noexcept(
    allocator_type::propagate_on_container_move_assignment::value &&
    is_nothrow_move_assignable<allocator_type>::value);

Если alloc_traits::propagate_on_container_move_assignment::value - true, выгружает ресурсы, перемещает назначает распределители и передает ресурсы из c.

Если alloc_traits::propagate_on_container_move_assignment::value - falseи get_allocator() == c.get_allocator(), сбрасывает ресурсы и передает ресурсов из c.

Если alloc_traits::propagate_on_container_move_assignment::value - falseи get_allocator() != c.get_allocator(), move присваивает каждому c[i].

void swap(C& c)
  noexcept(!allocator_type::propagate_on_container_swap::value ||
           __is_nothrow_swappable<allocator_type>::value);

Если alloc_traits::propagate_on_container_swap::value есть true, свопы распределители. Независимо, своп ресурсов. Undefined, если распределители не равны, а propagate_on_container_swap::value - false.