Почему С++ 0x `noexcept` проверяется динамически?

Мне интересно узнать о noexcept в С++ 0x FCD. throw(X) устарел, но noexcept, похоже, делает то же самое. Есть ли причина, по которой noexcept не проверяется во время компиляции? Кажется, было бы лучше, если бы эти функции были проверены статически, что они называли только функции бросания в блоке try.

Ответ 1

Если я помню, что бросок был устаревшим, потому что не существует способа указать все исключения, которые может вызвать функция шаблона. Даже для не-шаблонных функций вам понадобится предложение throw, потому что вы добавили некоторые трассы.

С другой стороны, компилятор может оптимизировать код, который не генерирует исключения. См. "" Дебаты на noexcept, часть I "(вместе с частями II и III) для подробного обсуждения. Главным моментом является:

Огромный опыт последних двух десятилетий показывает, что на практике полезны только две формы спецификаций исключений:

  • Отсутствие явной спецификации исключения, которая обозначает функцию, которая может вызывать любое исключение:

    int func(); //might throw any exception
    
  • Функция, которая никогда не выбрасывает. Такая функция может быть указана спецификацией throw():

    int add(int, int) throw(); //should never throw any exception
    

Ответ 2

В основном, это проблема компоновщика, комитет по стандартизации не хотел нарушать ABI. (Если бы это зависело от меня, я бы сделал это, все, что ему действительно нужно, это перекомпиляция библиотеки, у нас есть эта ситуация уже с поддержкой потоков, и она управляема.)

Подумайте, как это получится. Предположим, что требования были

  • каждый деструктор неявно noexcept(true)
    • Возможно, это должно быть строгим требованием. Бросающие деструкторы всегда являются ошибкой.
  • каждый extern "C" неявно noexcept(true)
    • Тот же аргумент снова: исключения на C-land всегда являются ошибкой.
  • любая другая функция неявно noexcept(false), если не указано иное
  • Функция noexcept(true) должна обернуть все ее вызовы noexcept(false) в try{}catch(...){}
    • По аналогии, метод const не может вызывать неконстантный метод.
  • Этот атрибут должен проявляться как отдельный тип при разрешении перегрузки, совместимости указателей функций и т.д.

Звучит разумно, правильно?

Чтобы реализовать это, компоновщик должен различать версии функций noexcept(true) и noexcept(false), так как вы можете перегружать версии функций-членов const и const.

Итак, что это значит для имен? Чтобы быть обратно совместимым с существующим объектным кодом, нам потребовалось бы, чтобы все существующие имена были интерпретированы как noexcept(false) с дополнительным изменением для версий noexcept(true).

Это подразумевает, что мы не можем связываться с существующими деструкторами, если заголовок не изменен, чтобы пометить их как noexcept(false)

  • это нарушит совместимость,
  • это, вероятно, должно быть невозможно, см. пункт 1.

Я говорил об этом с членом комитета по стандартизации, и он говорит, что это было поспешное решение, мотивированное главным образом ограничением операций перемещения в контейнерах (иначе вы могли бы потерять элементы после броска, что нарушает основная гарантия). Имейте в виду, что это человек, чья заявленная философия дизайна заключается в том, что код непереносимости ошибок хорош. Сделай свои выводы.

Как я уже сказал, я бы сломал ABI, предпочитая сломать язык. noexcept является лишь незначительным улучшением на старом пути. Статическая проверка всегда лучше.

Ответ 3

Обратите внимание, что noexcept проверяет исключение, созданное сбойными dynamic_cast и typeid, применяемыми к указателю null, которое может быть выполнено только во время выполнения. Другие тесты действительно могут быть выполнены во время компиляции.

Ответ 4

Как указывали другие ответы, такие выражения, как dynamic_cast, могут быть запущены, но могут быть проверены только во время выполнения, поэтому компилятор не может сказать наверняка во время компиляции.

Это означает, что во время компиляции компилятор может либо отпустить их (т.е. не проверять время компиляции), либо предупредить, либо отклонить прямо (что было бы бесполезно). Это оставляет предупреждение как единственное разумное решение для компилятора.

Но это все еще не очень полезно - предположим, что у вас есть dynamic_cast, который, по какой-либо причине, вы знаете, никогда не потерпит неудачу и выбросит исключение, потому что ваша программа написана так. Компилятор, вероятно, этого не знает и выдает предупреждение, которое становится шумом, который, вероятно, просто отключается программистом за то, что он бесполезен, отрицая точку предупреждения.

Аналогичная проблема заключается в том, что у вас есть функция, которая не указана с помощью noexcept (т.е. может генерировать исключения), которую вы хотите вызвать из многих функций, а некоторые noexcept, а некоторые нет. Вы знаете, что функция никогда не столкнется с обстоятельствами, вызванными функциями noexcept, но опять же компилятор не делает: более бесполезные предупреждения.

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

Ответ 5

Рассмотрим функцию

void fn() noexcept
{
   foo();
   bar();
}

Можете ли вы статически проверить, правильно ли это? Вам нужно будет знать, будут ли foo или bar бросать исключения. Вы можете заставить все вызовы функций находиться внутри блока try {}, что-то вроде этого

void fun() noexcept
{
    try
    {
         foo();
         bar();
    }
    catch(ExceptionType &)
    {
    }
}

Но это неправильно. У нас нет способа узнать, что foo и bar будут только бросать этот тип исключения. Чтобы убедиться, что мы поймаем что-нибудь, нам нужно будет использовать "...". Если вы поймаете что-нибудь, что вы собираетесь делать с любыми ошибками, которые вы поймаете? Если здесь возникает непредвиденная ошибка, единственное, что нужно сделать - это прервать программу. Но это по существу то, что делает проверка времени выполнения по умолчанию.

Короче говоря, предоставляя достаточно подробностей, чтобы доказать, что данная функция никогда не будет бросать неправильные исключения, вы получите подробный код в тех случаях, когда компилятор не может быть уверен, будет ли функция выдавать неправильный тип. Полезность этого статического доказательства, вероятно, не стоит усилий.

Ответ 6

В функциональности noexcept и throw() есть несколько совпадений, но они действительно идут с противоположных сторон.

throw() был о правильности, и ничего больше: способ указать поведение, если было вызвано непредвиденное исключение.

Основной целью noexcept является оптимизация: она позволяет/поощряет компилятор оптимизировать вокруг предположения, что никакие исключения не будут выбрасываться.

Но да, на практике, они не далеко от двух разных имен для одного и того же. Мотивации, стоящие за ними, разные.