Почему старая спецификация пустого броска была переписана с новым синтаксисом `noexcept`?

Название говорит само за себя: почему C++ отказался от полностью удовлетворяющей, полезной, пустой спецификации throw throw(), чтобы заменить ее другим синтаксисом, добавив новое ключевое слово noexcept?

Спецификация пустого броска — это «гарантия генерировать только эти перечисленные исключения» (написано throw(X,Y,Z)), но с нулевым перечислением исключений: вместо генерирования X, Y или Z (и производных типов) вы можете генерировать пустой набор: это является гарантией того, что функция никогда ничего не выдает вызывающей стороне, другими словами, никогда не выбрасывает или "не выбрасывает" спецификацию.

Это безвозмездно созданный новый код с использованием практически того же инструмента, выражающий то же обещание, несовместимый со старым кодом, и старый код объявлен устаревшим, а затем запрещен, нарушая обратную совместимость без видимой причины?

Что вызвало такую ​​ненависть к throw()?

Насколько я могу судить, только к старому небезопасному gets и глупому бесполезному неявному int относились столь же жестко.

РЕДАКТИРОВАТЬ:

Предполагаемый «дубликат» основан на ложном заявлении.

В так называемой "динамической спецификации исключений" нет ничего "динамического". Это то, что я больше всего ненавижу в новой спецификации throw: смысл бессмысленной терминологии, противопоставляющей "динамический" и "статический" .


person curiousguy    schedule 27.10.2019    source источник
comment
Возможный дубликат Разница между спецификатором throw() C++03 C++11 noexcept   -  person JaMiT    schedule 27.10.2019
comment
Давайте продолжим это обсуждение в чате.   -  person Nicol Bolas    schedule 27.10.2019


Ответы (1)


Это безвозмездно создало новый код, используя по существу тот же инструмент, выражая то же обещание, несовместимое со старым кодом.

Исправление: если функция нарушает спецификацию динамического исключения, вызывается 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
comment
который вызывает неожиданный обработчик Да, действительно, я совершенно забыл об этом, вероятно, потому, что я не видел никакого практического применения set_unexpected. Таким образом, неожиданный обработчик в основном является усложнением, которое ничего не меняет для пустой спецификации throw. - person curiousguy; 27.10.2019
comment
Интересный ответ, но здесь я не согласен: Это не то, что может сделать спецификатор динамического исключения. На самом деле они могут это сделать: throw(...) - person curiousguy; 27.10.2019
comment
@curiousguy: у меня нет копии C++98/03, но я не помню, чтобы throw(...) был допустимым синтаксисом. Грамматика похоже, требует списка типов- ids, и я не думаю, что ... является идентификатором типа. - person Nicol Bolas; 27.10.2019
comment
Я уверен, что throw(...) существует в C++ std, за который проголосовали в 97, за исключением того, что он бесполезен и никто никогда его не использовал. - person curiousguy; 27.10.2019
comment
@curiousguy: Моя память о таком синтаксисе ограничена, но я смутно его помню, поэтому я удалил абзац. Я также добавил некоторые подробности о unexpected и тому подобном в отношении первоначального предложения noexcept. - person Nicol Bolas; 27.10.2019
comment
@curiousguy: Какие накладные расходы? Из-за чего? Я объяснил это в следующем абзаце. Сам текст написан для людей, которые, как предполагается, уже знакомы с подобными вещами, поэтому он не прописан. - person Nicol Bolas; 27.10.2019
comment
Нет, это не прописано, потому что им будет трудно это объяснить. Где будет эта попытка-поймать? Что бы он сделал? В любом случае, вы цитируете статью, оправдывающую семантику MS, а не семантику C++, так что... - person curiousguy; 27.10.2019
comment
@curiousguy: Где должен быть этот try-catch? throw() требует, чтобы в случае, если функция пытается сгенерировать исключение, стек вызовов должен быть раскручен до начала этой функции, а затем вызывается unexpected. Чтобы выполнить эту раскрутку, реализация должна создать эквивалент блока try/catch вокруг функции. Отсюда накладные расходы. Я не знаю, что такое семантика MS; если вы имеете в виду Microsoft (и, предположительно, VC++), документы написаны не людьми, имеющими какое-либо отношение к Microsoft. Я не знаю, почему вы подняли их в этом контексте, поскольку это не имеет значения. - person Nicol Bolas; 27.10.2019
comment
1) Да и что это за накладные расходы? По чему? Какова альтернатива? И сколько накладных расходов? и где? 2) Да, я имел в виду MSVC++, который является текущей темой... - person curiousguy; 27.10.2019
comment
@curiousguy: Да, и какие это накладные расходы? Над чем? Какова альтернатива? Альтернатива... не раскручивать стек. Я не уверен, зачем вам нужно было это прописано. И сколько накладных расходов? и где? Это трудно определить количественно и по сути не имеет значения; он есть, и его не должно быть. Не платите за то, чем не пользуетесь. Я имел в виду MSVC++, который является текущей темой... Ничто в этом вопросе, моем ответе или чем-либо, на что я ссылался в этом ответе, не имеет никакого отношения к какому-либо конкретному компилятору. Так что да, не в тему. - person Nicol Bolas; 27.10.2019
comment
noexcept означает немедленное завершение работы, если оно попытается сгенерировать исключение. Единственным способом немедленного завершения является вызов abort или _exit. Если пользователь заменил terminate, функция может сделать много вещей, прежде чем выйти. Это означает, что все состояние переменных должно быть в порядке. - person curiousguy; 10.11.2019
comment
@curiousguy: Да, но стек не обязательно раскручивать. Это означает, что вам не нужно создавать оборудование, необходимое для раскручивания стека до этой точки. Это вопрос накладных расходов. - person Nicol Bolas; 10.11.2019