Ответ 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
.