Урок №184. Непойманные исключения, обработчики catch-all и спецификации исключений

  Юрий  | 

    | 

  Обновл. 1 Дек 2018  | 

 2015

 ǀ   3 

До этого момента вы уже должны были разобраться с тем, как работают исключения. В этом уроке мы рассмотрим еще несколько интересных тем, связанных с исключениями.

Непойманные исключения

В предыдущем уроке мы научились делегировать обработку исключений на caller-а (или на другую функцию, которая идет выше в стеке вызовов). В следующем примере mySqrt() выбрасывает исключение и предполагает, что кто-то его обработает. Но что произойдет, если никто этого не сделает?

Вот наша программа вычисления квадратного корня числа без блока try в функции main():

Теперь предположим, что пользователь ввёл -5, и mySqrt(-5) сгенерировало исключение. Функция mySqrt() не обрабатывает свои исключения самостоятельно, поэтому стек начинает раскручиваться и точка выполнения возвращается обратно в main(). Но, поскольку в main() также нет обработчика исключений, выполнение main() и целой программы прекращается.

Когда main() завершает своё выполнение с необработанным исключением, то операционная система обычно уведомляет нас о том, что произошла ошибка необработанного исключения. Как она это сделает — зависит от каждой отдельно:

   либо выведет сообщение об ошибке;

   либо откроет диалоговое окно с ошибкой;

   либо просто сбой.

Это то, что мы не должны допускать, как программисты!

Обработчики всех типов исключений


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

К счастью, C++ предоставляет нам механизм обнаружения/обработки всех типов исключений — обработчик catch-all. Обработчик catch-all работает так же, как и обычный блок catch, за исключением того, что вместо обработки исключений определенного типа данных, он использует эллипсис (…) в качестве типа данных.

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

Поскольку для типа int не существует специального обработчика catch, то обработчик catch-all ловит это исключение. Следовательно, результат:

We caught an exception of an undetermined type!

Обработчик catch-all должен находится последним в цепочке блоков catch. Это делается для того, чтобы исключения сначала могли быть пойманы обработчиками catch, адаптированными к конкретным типам данных (если они вообще существуют). В Visual Studio это контролируется, насчет других компиляторов — не уверен, есть ли такое ограничение.

Часто блок обработчика catch-all оставляют пустым:

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

Использование обработчика catch-all в main()

Рассмотрим следующую программу:

В этом случае, если runGame() или любая другая из функций, которые вызываются в runGame(), выбросит исключение, которое не будет поймано функциями в стеке выше, то в конечном итоге оно попадет в обработчик catch-all. Это предотвратит завершение выполнения функции main() и даст нам возможность вывести сообщение с указанием ошибки на наше усмотрение, а затем сохранить состояние пользователя до выхода из программы. Это может быть полезно для обнаружения и устранения непредвиденных проблем.

Спецификации исключений


Эту тему можете рассматривать как дополнительное чтиво, так как спецификации исключений редко используются на практике, плохо поддерживаются компиляторами, а Бьёрн Страуструп (создатель C++) считает их неудачным экспериментом.

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

Существуют три типа спецификации исключений, каждый из которых использует так называемый синтаксис throw (…).

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

Обратите внимание, doSomething() все еще может генерировать исключения, только обрабатывать она должна их самостоятельно. Любая функция, объявленная с использованием throw() (как в примере выше), должна немедленно прекратить выполнение целой программы, если она попытается сгенерировать исключение, которое приведет к раскручиванию стека. Другими словами, мы сообщаем, что все исключения функции doSomething() обрабатывать будет функция doSomething() самостоятельно.

Во-вторых, мы можем использовать оператор throw с указанием типа исключения, которое может генерировать эта функция:

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

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

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (20 оценок, среднее: 5,00 из 5)
Загрузка...

Комментариев: 3

  1. Аватар kmish:

    Копнул по спецификациям (https://en.cppreference.com/w/cpp/language/except_spec):

    throw( ) (удалена в C++20)
    throw(typeid, typeid, …) (удалена в C++17)
    throw(…) просто избыточна:
    int doSomething() throw(…);
    равно
    int doSomething();

    Начиная с С++17 рекомендуется использовать вместо throw() noexcept(true):
    int doSomething() noexcept(true);
    В целом, пожалуй, и правда, лучше не использовать.

  2. Аватар Дмитрий Тормосин:

    Спасибо за четкое разъяснение материала.

    1. Юрий Юрий:

      Пожалуйста 🙂

Добавить комментарий для Дмитрий Тормосин Отменить ответ

Ваш E-mail не будет опубликован. Обязательные поля помечены *