Это безвозмездно создало новый код, используя по существу тот же инструмент, выражая то же обещание, несовместимое со старым кодом.
Исправление: если функция нарушает спецификацию динамического исключения, вызывается std::unexpected
, который вызывает неожиданный обработчик, который по умолчанию вызывает std::terminate
. Но обработчик можно заменить пользовательской функцией. Если функция нарушает noexcept
, std::terminate
вызывается напрямую.
Обещания разные. throw()
означало "может делать неожиданные вещи, если попытается создать исключение". noexcept
означает "немедленно завершится, если попытается создать исключение".
Только в C++17 throw()
стало точно эквивалентно noexcept
. Это произошло после 6 лет, когда спецификации исключений (включая throw()
) устарели.
Следует отметить, что разница в unexpected
была явно указана в первые статьи о noexcept
. Конкретно:
Обратите внимание, что полезность noexcept(true)
в качестве подсказки по оптимизации выходит далеко за рамки узкого случая, представленного N2855. На самом деле это выходит за рамки построения перемещения: когда компилятор может с уверенностью обнаруживать операции, не вызывающие исключения, он может оптимизировать большую часть кода и/или данных, предназначенных для обработки исключений. Некоторые компиляторы уже делают это для спецификаций throw()
, но поскольку они несут накладные расходы на неявный блок try/catch для обработки непредвиденных исключений, преимущества ограничены.
Неявный блок try/catch необходим, поскольку unexpected
должен вызываться после раскручивания стека до функции throw()
. По сути, каждая функция throw()
выглядит так:
void func(params) throw()
try
{
<stuff>
}
catch(...)
{
std::unexpected();
}
Таким образом, когда исключение пытается покинуть функцию throw()
, оно перехватывается и стек раскручивается. Более того, каждая функция throw()
должна иметь встроенный механизм обработки исключений. Таким образом, любая стоимость блока try/catch
будет понесена каждой функцией throw()
.
В первой версии noexcept
исключение вызывалось прямым UB, в то время как более поздние версии переключились на std::terminate
. Но даже при этом нет гарантии раскрутки. Таким образом, реализации могут реализовать noexcept
более эффективным способом. Когда система просматривает стек в поисках ближайшего предложения catch
, если оно попадает в нижнюю часть функции noexcept
, она может перейти прямо к завершению без какого-либо механизма перехвата.
Что вызвало такую ненависть к throw()?
Исправление: неиспользование конструкции не означает злого умысла. Особенно, когда изобретение новой конструкции позволит избежать нарушения совместимости, как показано выше.
Следует отметить, что throw()
считается эквивалентным функции noexcept
с точки зрения выражения noexcept
. То есть вызов функции throw()
не вызывает исключений, а выражение noexcept(empty_throw())
приведет к true
.
Следует также отметить, что в любом случае потребуется новое ключевое слово. Почему? Потому что throw(<stuff>)
в C++98/03 уже имело значение. noexcept(<stuff>)
имеет совсем другое значение для <stuff>
. Попытка поместить noexcept
внутри спецификатора throw
будет... сложной с точки зрения синтаксического анализа.
Кроме того, теперь вы можете использовать это новое ключевое слово в качестве обобщенного выражения: noexcept(<expression>)
преобразуется в true
, если ни один из вызовов внутри него не вызовет исключений. Это позволяет вам выполнять условную логику на основе того, будут ли вещи вызывать исключения. Вам понадобится новое ключевое слово, чтобы сделать что-то подобное (или вам придется сделать уродливый синтаксис, а в C++ этого слишком много).
person
Nicol Bolas
schedule
27.10.2019