Ответ 1
Я считаю, что мы смотрим на дефекты стандартов. Спецификация noexcept
, если она применяется к оператору присваивания перемещения, несколько сложна. И я считаю, что это утверждение верно, если мы говорим о basic_string
или vector
.
На основе [container.requirements.general]/p7 мой английский перевод того, что должен делать оператор назначения перемещения контейнера:
C& operator=(C&& c)
Если
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]
.
Примечания:
-
alloc_traits
относится кallocator_traits<allocator_type>
. -
Когда
alloc_traits::propagate_on_container_move_assignment::value
естьtrue
, оператор назначения перемещения может быть указанnoexcept
, потому что все, что он собирается, освобождает текущие ресурсы, а затем извлекает ресурсы из источника. Также в этом случае распределитель также должен быть назначен на перенос, а назначение переноса должно бытьnoexcept
для назначения перемещения контейнераnoexcept
. -
Когда
alloc_traits::propagate_on_container_move_assignment::value
равноfalse
, и если два распределителя равны, то он будет делать то же самое, что и # 2. Однако никто не знает, равны ли распределители равными до времени выполнения, поэтому вы не можете использоватьnoexcept
для этой возможности. -
Когда
alloc_traits::propagate_on_container_move_assignment::value
равноfalse
, и если два распределителя не равны, тогда нужно переместить назначение каждого отдельного элемента. Это может включать добавление емкости или узлов к цели и, следовательно, внутреннеnoexcept(false)
.
Итак, вкратце:
C& operator=(C&& c)
noexcept(
alloc_traits::propagate_on_container_move_assignment::value &&
is_nothrow_move_assignable<allocator_type>::value);
И я не вижу зависимости от C::value_type
в приведенной выше спецификации, поэтому я считаю, что он должен одинаково хорошо относиться к std::basic_string
, несмотря на то, что С++ 11 указывает иначе.
Обновление
В комментариях ниже Коломбо правильно указывает, что вещи постепенно меняются все время. Мои комментарии выше относятся к С++ 11.
Для проекта С++ 17 (который кажется стабильным на данный момент) ситуация несколько изменилась:
-
Если
alloc_traits::propagate_on_container_move_assignment::value
-true
, спецификация теперь требует назначения перемещенияallocator_type
, чтобы не генерировать исключения (17.6.3.5 [allocator.requirements]/p4). Поэтому больше не нужно проверятьis_nothrow_move_assignable<allocator_type>::value
. -
alloc_traits::is_always_equal
. Если это так, то во время компиляции можно определить, что точка 3 выше не может выбраться, потому что ресурсы могут быть переданы.
Таким образом, новая спецификация noexcept
для контейнеров может быть:
C& operator=(C&& c)
noexcept(
alloc_traits::propagate_on_container_move_assignment{} ||
alloc_traits::is_always_equal{});
И, для std::allocator<T>
, alloc_traits::propagate_on_container_move_assignment{}
и alloc_traits::is_always_equal{}
являются истинными.
Также теперь в проекте С++ 17 оба назначения vector
и string
переместить несут именно эту спецификацию noexcept
. Однако другие контейнеры несут вариации этой спецификации noexcept
.
Самая безопасная вещь, которую нужно делать, если вы беспокоитесь об этой проблеме, - это проверить явные специализации контейнеров, которые вам интересны. Я сделал именно это для container<T>
для VS, libstdС++ и libС++ здесь:
http://howardhinnant.github.io/container_summary.html
Этот опрос составляет около года, но, насколько я знаю, все еще действует.