Почему std::vector
operator[]
, front
и back
функции-члены не указаны как noexcept
?
Почему оператор векторного доступа не указан как noexcept?
Ответ 1
Стандартная политика noexcept
заключается в том, чтобы отмечать только те функции, которые не могут или не должны терпеть неудачу, но не те, которые просто указываются не для исключения исключений. Другими словами, все функции, которые имеют ограниченный домен (передают неправильные аргументы и вы получаете поведение undefined), не являются noexcept
, даже если они не заданы для throw.
Функции, которые будут отмечены, такие вещи, как swap
(не должны прерываться, потому что безопасность исключений часто зависит от этого) и numeric_limits::min
(не может быть отказано, возвращает константу примитивного типа).
Причина в том, что разработчики могут захотеть предоставить специальные отладочные версии своих библиотек, которые вызывают различные ситуации поведения undefined, так что тестовые среды могут легко обнаружить ошибку. Например, если вы используете индекс вне привязки с vector::operator[]
или вызываете front
или back
на пустой вектор. Некоторые реализации хотят создать там исключение (которому им разрешено: поскольку это поведение undefined, они могут что-либо делать), но стандартная функция noexcept
для этих функций делает это невозможным.
Ответ 2
В качестве бесплатного ответа @SebastianRedl: зачем вам нужно noexcept
?
noexcept и std::vector
Как вы могли бы знать, vector
имеет свою емкость. Если он заполнен при push_back
, он выделит большую память, скопирует (или переместит, если С++ 11) все существующие элементы в новую соединительную линию, а затем добавит новый элемент обратно.
Но что, если исключение выбрасывается при распределении памяти или копировании элемента в новую соединительную линию?
-
Если при распределении памяти выбрано исключение, вектор находится в исходном состоянии. Это прекрасно, просто повторите выброс исключения и дайте пользователю обработать его.
-
Если во время копирования существующих элементов выбрано исключение, все скопированные элементы будут уничтожены вызовом деструктора, выделенная соединительная линия будет освобождена, а исключение выбрано для обработки кодом пользователя. (1)
После уничтожения всего, вектор возвращается в исходное состояние. Теперь безопасно бросать исключение, чтобы позволить пользователю обрабатывать его, не утечка какого-либо ресурса.
noexcept и move
Приходите в эпоху С++ 11, у нас есть мощное оружие под названием move
. Это позволяет нам украсть ресурсы из неиспользуемых объектов. std::vector будет использовать move
, когда ему нужно увеличить (или уменьшить) емкость, если операция move
noexcept.
Предположим, что во время перемещения выбрано исключение, предыдущая соединительная линия не такая же, как раньше move
: ресурсы украдены, оставляя вектор в разбитом состоянии. Пользователь не может обработать исключение, потому что все находится в недетерминированном состоянии.
Вот почему std::vector
полагается на move constructor
как noexcept.
Это демонстрация того, как клиентский код будет полагаться на noexcept
как спецификацию интерфейса. Если позже требование noexcept
не будет выполнено, любой код будет зависеть от того, будет ли он нарушен.
Почему бы просто не отметить все функции как noexcept
?
Короткий ответ: безопасный код исключения трудно записать.
Длинный ответ: noexcept
установить строгий предел для разработчиков, которые реализуют интерфейс. Если вы хотите удалить noexcept
из интерфейса, клиентский код может быть разбит как приведенный выше векторный пример; но если вы хотите создать интерфейс noexcept
, вы можете сделать это в любое время.
Таким образом, только при необходимости отметьте интерфейс как noexcept
.
В Going Native 2013 Скотт Майерс говорил о ситуации выше, без noexcept
, здравомыслие программы потерпит неудачу.
Я также написал блог об этом: http://xinhuang.github.io/when-to-use-noexcept-and-when-to-not/
Ответ 3
Короче говоря, есть функции, указанные с помощью или без noexcept
. Он предназначен, потому что они разные. Принцип: функция с заданием undefined, указанная (например, из-за неправильных аргументов) не должна быть с noexcept
.
В этой статье явно указаны эти члены без noexcept
. В качестве примеров использовались некоторые члены vector
:
Примеры функций с широкими контрактами были бы
vector<T>::begin()
иvector<T>::at(size_type)
. Примеры функций, не имеющих широкого контракта, были быvector<T>::front()
иvector<T>::operator[](size_type)
.
См. этот документ для первоначальной мотивации и подробного обсуждения. Наиболее очевидной реалистичной проблемой здесь является тестируемость.