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

  Юрий Ворон  | 

    | 

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

 433

 ǀ   1 

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

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

В предыдущем уроке мы научились делегировать обработку исключений на 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 (6 оценок, среднее: 5,00 из 5)
Загрузка...

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

  1. Дмитрий Тормосин:

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

Добавить комментарий

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