Обработка исключений
Иногда при выполнении программы возникают ошибки, которые трудно предусмотреть или предвидеть, а иногда и вовсе невозможно. Например, при передачи файла по сети может неожиданно оборваться сетевое подключение. такие ситуации называются исключениями . Язык C# предоставляет разработчикам возможности для обработки таких ситуаций. Для этого в C# предназначена конструкция try. catch. finally .
При использовании блока try. catch..finally вначале выполняются все инструкции в блоке try . Если в этом блоке не возникло исключений, то после его выполнения начинает выполняться блок finally . И затем конструкция try..catch..finally завершает свою работу.
Если же в блоке try вдруг возникает исключение, то обычный порядок выполнения останавливается, и среда CLR начинает искать блок catch , который может обработать данное исключение. Если нужный блок catch найден, то он выполняется, и после его завершения выполняется блок finally.
Если нужный блок catch не найден, то при возникновении исключения программа аварийно завершает свое выполнение.
Рассмотрим следующий пример:
В данном случае происходит деление числа на 0, что приведет к генерации исключения. И при запуске приложения в режиме отладки мы увидим в Visual Studio окошко, которое информирует об исключении:

В этом окошке мы видим, что возникло исключение, которое представляет тип System.DivideByZeroException , то есть попытка деления на ноль. С помощью пункта View Details можно посмотреть более детальную информацию об исключении.
И в этом случае единственное, что нам остается, это завершить выполнение программы.
Чтобы избежать подобного аварийного завершения программы, следует использовать для обработки исключений конструкцию try. catch. finally . Так, перепишем пример следующим образом:
В данном случае у нас опять же возникнет исключение в блоке try, так как мы пытаемся разделить на ноль. И дойдя до строки
выполнение программы остановится. CLR найдет блок catch и передаст управление этому блоку.
После блока catch будет выполняться блок finally.
Таким образом, программа по-прежнему не будет выполнять деление на ноль и соответственно не будет выводить результат этого деления, но теперь она не будет аварийно завершаться, а исключение будет обрабатываться в блоке catch.
Следует отметить, что в этой конструкции обязателен блок try . При наличии блока catch мы можем опустить блок finally:
И, наоборот, при наличии блока finally мы можем опустить блок catch и не обрабатывать исключение:
Однако, хотя с точки зрения синтаксиса C# такая конструкция вполне корректна, тем не менее, поскольку CLR не сможет найти нужный блок catch, то исключение не будет обработано, и программа аварийно завершится.
Обработка исключений и условные конструкции
Ряд исключительных ситуаций может быть предвиден разработчиком. Например, пусть в программе есть метод, который принимает строку, конвертирует ее в число и вычисляет квадрат этого числа:
Если пользователь передаст в метод не число, а строку, которая содежит нецифровые символы, то программа выпадет в ошибку. С одной стороны, здесь как раз та ситуация, когда можно применить блок try..catch , чтобы обработать возможную ошибку. Однако гораздо оптимальнее было бы проверить допустимость преобразования:
Метод int.TryParse() возвращает true , если преобразование можно осуществить, и false — если нельзя. При допустимости преобразования переменная x будет содержать введенное число. Так, не используя try. catch можно обработать возможную исключительную ситуацию.
С точки зрения производительности использование блоков try..catch более накладно, чем применение условных конструкций. Поэтому по возможности вместо try..catch лучше использовать условные конструкции на проверку исключительных ситуаций.
Кое-что об исключениях в C++
При работе программ возникают так называемые исключительные ситуации, когда дальнейшее нормальное выполнение приложения становится невозможным. Причиной исключительных ситуаций могут быть как ошибки в программе, так и неправильные действия пользователя, неверные данные и т.д.
Обычные конструкции, необходимые для проверки данных, делают более-менее серьезную программу сложночитабельной. Более того, программисту очень сложно отледить все исключительные ситуации.
Сдесь на помощь программисту приходят такие средства языка, как:
-защищенные блоки ( try -блоки) и перехваты исключений ( catch -блоки);
-инициализация исключений (инструкция throw ).
блок try-catch
Простейший формат защищенного блока имеет вид:
Важно! Многоточие является частью синтаксиса языка!
Работает эта конструкция так.
Выполняются инструкции, входящие в состав блока try (защищенный блок). Если при их выполнении исключение не возбуждается (в C++ чаще используется термин «выброс исключения»), то блок catch пропускается. При выбросе исключения выполнение защищенного блока прекращается, и начинают работать инструкции, записанные в блоке catch .
Основной смысл этих инструкций – корректная обработка исключительной ситуации. Кроме того, в блок catch имеет смысл поместить код, который освобождает ресурсы, захваченные выполнившимися инструкциями из блока try . После окончания работы блока catch исключение считается обработанным, и управление передается на первую инструкцию, следующую за конструкцией try … catch .
Пример: перехват системного исключения «деление на ноль»
int x = 0;
try <
std::cout <<2/x; //Здесь произойдет выброс исключения
// Последующие операторы выполняться не будут
>
catch (. ) <
std::cout << «Division by zero» << std::endl;
Вот и все, и совсем не больно)
throw
Гораздо более интересным является механизм создания собственных исключений. Для их возбуждения используется оператор throw .
Тип выражения, указанного в операторе throw , определяет тип исключительной ситуации, а значение может быть передано обработчику прерываний. Этот механизм, заявленный как стандартный, представляется весьма экзотическим без использования механизма классов. И только
использование стандартных классов-исключений или разработка собственных классов
позволяют в полной мере оценить все возможности такого подхода.
Соответственно, полный формат защищенного блока имеет вид:
try <операторы защищенного блока>
Первый формат используется, если нам надо указать тип перехватываемого исключения, но не нужно обрабатывать связанное с этим исключением значение (это достигается при использовании второго формата оператора catch ). Наконец, третий формат оператора catch позволяет обработать все исключения.
Обработка исключений, возбужденных оператором throw , идет по следующей схеме:
1. Создается статическая переменная со значением, заданным в операторе throw .
Она будет существовать до тех пор, пока исключение не будет обработано.
Если переменная-исключение является объектом класса, при ее создании работает конструктор копирования.
2. Завершается выполнение защищенного try -блока: раскручивается
стек подпрограмм,
вызываются деструкторы для тех объектов, время жизни которых истекает и т.д.
3. Выполняется поиск первого из catch -блоков, который пригоден для обработки созданного
исключения.
Поиск ведется по следующим критериям:
— если тип, указанный в catch -блоке, совпадает с типом созданного исключения,
или является ссылкой на этот тип;
— класс, заданный в catch -блоке, является предком класса, заданного в throw ,
и наследование выполнялось с ключом доступа public ;
— указатель, заданный в операторе throw , может быть преобразован по стандартным правилам к указателю, заданному в catch -блоке.
— в операторе throw задано многоточие.
Если нужный обработчик найден, то ему передается управление и, при необходимости,
значение оператора throw . Оставшиеся catch -блоки,
относящиеся к защищенному блоку, в котором было создано исключение, игнорируются.
Из указанных правил поиска следует, что очень важен порядок расположения catch -блоков.
Так, блок catch(…) должен стоять последним в списке, а блок catch (void *) – после всех блоков с указательными типами.
Если ни один из catch -блоков, указанных после защищенного блока, не сработал, то исключение считается необработанным. Его обработка может быть продолжена во внешних блоках try (если они, конечно, есть).
В конце оператора catch может стоять оператор throw без параметров. В этом случае работа catch -блока считается незавершенной а исключение – не обработанным до конца, и происходит поиск соответствующего обработчика на более высоких уровнях.
Если оператор throw был вызван вне защищенного блока (что чаще всего случается, когда исключение возбуждается в вызванной функции), или если не был найден ни один подходящий обработчик этого исключения, то вызывается стандартная функция terminate() . Она, в свою очередь, вызывает функцию abort() для завершения работы с приложением. Единственное, что доступно программисту в этом случае – зарегистрировать с помощью функции set_terminate свою функцию, которая будет выполняться перед аварийным завершением работы.
Например:
void MyTerminate() <
std::cout << «An error occured!» << std::endl;
exit(-1);
>
int main ()
<
set_terminate(MyTerminate);
throw 0;
>
Преимущества и недостатки работы с исключениями
Возбуждение исключения не является единственным способом сообщить о возникновении
внештатной ситуации в ходе выполнения программы. Просигнализировать о возникшей ошибке
могут и другие механизмы, например:
— занесение информации о состоянии программы в специальную переменную или поле класса и использование специальных механизмов доступа к этой информации;
— задание таких спецификаций для функций, которые сигнализировали бы о наступлении
нештатной ситуации (например, использование булевских функций, которые возвращали бы
значение false при возникновении таких ситуаций);
— использование стандартных функций, сообщающих об ошибках, например perror() .
Выбор того или иного механизма реакции на нештатные ситуации достаточно сложен, поскольку
четких критериев не существует, да и не может существовать. Мы должны учитывать следующие соображения:
— игнорирование исключения приводит к аварийному завершению приложения,
а игнорирование другой информации проходит незаметно (хотя результаты могут быть и непредсказуемыми);
— если в программе есть большое число вызовов функции, которая может создать
нештатную ситуацию, то логичнее было бы включить их в один защищенный блок, нежели
выполнять проверку результатов для каждого вызова;
— с другой стороны, следует иметь в виду, что после возникновения исключительной ситуации и ее обработки работа защищенного блока не возобновляется.
Как обрабатывать исключения в С++: что такое throw, try и catch
При выполнении кода на C++ могут возникать разные ошибки, которые не позволяют программе выполнять свою работу. Для работы с ошибками или исключениями в C++ используются ключевые слова try , catch и throw .
Содержание статьи:
Вступление: виды исключений и знакомство с try, catch и throw в C++

Есть два вида исключений, с которыми вы можете столкнуться в процессе:
- Синхронные исключения. Этот тип ошибок программа может контролировать сама. Например, ошибки в коде, допущенные программистом или неправильные параметры ввода.
- Асинхронные исключения. Этот тип ошибок не связан напрямую с кодом и программа не может их контролировать. Например, это может быть сбой диска, ошибка при открытии файла, сокета, выделения блока памяти.
Когда происходит какое-то событие, прерывающее нормальное функционирование программы, C ++ обычно останавливается и выдает сообщение об ошибке. Когда это происходит, говорят, что C++ выбрасывает ошибку — throw an exception. Мы уже упоминали, что для работы с ошибками или исключениями в C++ используются определенные ключевые слова, давайте познакомимся с ними поближе:
- try : позволяет определить блок кода, который будет проверяться на наличие ошибок во время его выполнения;
- throw : нужен для создания и отображения исключений и используется для перечисления ошибок, которые генерирует функция, но не может самостоятельно обрабатывать исключения;
- catch — блок кода, который выполняется при возникновенииопределенного исключения в блоке try .
Давайте посмотрим, как выглядит пример кода в С++ с использованием try catch и throw :
Вкратце объясним, как работают операторы try и catch в С++ на примере этого блока . Мы используем блок try для тестирования определенных строк кода: если переменная age меньше 18, мы генерируем исключение и обрабатываем его в блоке catch .
С помощью catch мы перехватываем ошибку и прописываем способ ее обработки. Оператор принимает параметр: в примере используется переменная типа int myNum для вывода значения возраста.
Если все данные соответствуют установленным параметрам, то ошибки не возникает. Например, если указанный возраст будет больше 18, а не 15, как указано в примере, то блок catch просто пропускается.
Если ошибка присутствует, то оператор throw выбросит ошибку. В throw можно прописать любое значение и оператор может выдать текст с пояснением, например:
Или установить числовое значение, например, то код ошибки будет выглядеть следующим образом:
После такой большой вводной части подробно рассмотрим генерацию исключений и как их обрабатывать, примеры использования try и catch в С++, подробно расскажем про задачи операторов.
Генерируем исключения в C++
Исключения могут быть выброшены в любом месте кода. Для этого в блоке нужно прописать throw .
Этот оператор определяет тип исключения и может быть любым выражением. Также throw сигнализирует об ошибке в коде и выводит исключение в консоль.
Помимо использования оператора throw , есть еще один способ мониторить ошибки в коде. Он более традиционный, но давайте рассмотрим и его, чтобы лучше понять механику обработки ошибок с помощью операторов.
Обычно мы разбиваем программу на несколько функций или подпрограмм, чтобы сделать ее читабельной и простой для понимания. Получается, что программа будет иметь связанные вызовы функций. То есть одна функция использует ту информацию, которую ей предоставляет другая функция.
Здесь возникают основные проблемы с обработкой ошибок при использовании оператора if :
- Все задействованные функции должны возвращать одни и те же типы данных, например, целые числа. Из-за этого код становится длинным и громоздким, потому что приходится возвращать один и тот же вид данных.
- Глобальную переменную нужно проверить сразу же после вызова функции в обработчике или кэшировать. Потому что она может обновиться, если в дальнейшем произойдет другая ошибка.
- Обработка ошибки зависит от вызывающей стороны. Если исключение не обработать, оно может вызвать сбой в программе позже или программа продолжит работу неправильно.
Вот так выглядит обработка ошибок в коде при использовании оператора if :
А вот так будет выглядеть код с использованием try и catch в С++ (example):
По сравнению с несколькими строками кода в случае try и catch в С++ , предыдущий блок выглядит очень перегруженным и длинным. В целом при использовании оператора if обработка ошибок и программный код тесно взаимосвязаны. Из-за этого код становится беспорядочным, и трудно гарантировать, что все ошибки будут обработаны и программа будет работать нормально.
Метод try / catch , в свою очередь, обеспечивает четкое разделение между кодом, который знает об ошибке, и кодом, который знает, как обрабатывать ошибку. Таким образом, код, который находятся между этими операторами, может безопасно игнорировать ошибку.
Поэтому, запуская код в С++ Builder, лучше искать исключения с помощью try , catch и throw . Это сделает ваш код проще, чище и с меньшей вероятностью вы допустите ошибки в программе.
Ищем ошибки в коде
Для того чтобы проверить блок кода на ошибки и аномалии, используется оператор try . Так мы можем быть уверены, что если появится исключение в этой части кода, то try его заметит. Главная особенность оператора в том, что в отличие от if / else , которые смешиваются с обычным потоком данных, try отделяет обработку ошибок от обычного течения программы.
Блок try помещается вокруг кода, который может генерировать исключение, и закрывается другим оператором этой пары — catch . Код в блоке try / catch называется защищенным кодом , а синтаксис для использования связки этих операторов выглядит следующим образом:
С помощью метода try / catch можно перечислить и поймать сразу несколько видов исключений, если блок try вызывает несколько типов ошибок в разных ситуациях. Несмотря на то, что функция может генерировать множество исключений, вы можете обрабатывать не все, а только некоторые из них.
Обрабатываем ошибки с try и catch in С++
Блок catch , идущий в паре с оператором try , ловит и обрабатывает исключения. Чтобы указать, какой тип исключения вы хотите поймать и обработать, нужно прописать это в скобках после ключевого слова catch :
Приведенный выше код перехватит только исключение типа ExceptionName . Если вы хотите указать, что блок catch должен обрабатывать любой тип ошибок, который находит оператор try , просто поместите многоточие . между скобками:
Рассмотрим пример кода, в котором генерируется исключение деления на ноль:
Так как программа вызывает тип исключения const char * , в блоке catch необходимо указать const char * , чтобы ошибку можно было определить и обработать. Если скомпилировать и запустить этот блок кода, то в результате получим условие прописанное в throw :
Division by zero condition!
Как работают throw, try и catch в C++: примеры
Рассмотрим на примерах, как между собой взаимодействуют операторы throw , try и catch в С++. В блоке кода ниже приведен простой пример, демонстрирующий обработку исключений. Результат программы наглядно покажет, в какой последовательности происходит выполнение операторов :
В результате получается следующая последовательность:
Нужно не забывать прописывать одинаковые типы исключений в try / catch . Если исключение одного типа будет выброшено, а catch не сможет его поймать и обработать, то программа завершается ненормально:
В этом примере кода исключение является символом, но блок catch для захвата символа отсутствует. В результате блок вернет нам не исключение, а вот такое предупреждение:
С помощью try / catch можно указывать кастомные типы исключений, наследуя и переопределяя функциональность класса исключений. В примере ниже приведем код, который покажет, как вы можете использовать класс std :: exception для генерации собственной ошибки стандартным способом :
Результат выполнения кода выглядит так:
Еще немного о порядке обработке ошибок в C++
Когда мы прописываем операторы try / catch в коде, то исключение выбрасывается только при исполнении определенных условий. Рассмотрим как работают try , catch и throw в С++ на примере:
Пользователь может ввести число больше нуля, как и задумано. Тогда программа просто продолжит работать в нормальном режиме и пропустит блок с оператором catch . Допустим, пользователь ввел число 49. Тогда результат выполнения кода будет следующим:
Но пользователи не всегда действуют так, как задумывал разработчик. Поэтому оператор catch нужен нам для того, чтобы программа не сломалась от непредвиденных значений, а могла нормально функционировать и дальше. Поэтому если пользователь введет число меньше нуля, то после того, как try обнаружит непредусмотренное значение, заработают операторы catch и throw , и программа выдаст такое значение:
Таким образом, строки кода с catch и throw выполняются только тогда, когда try обнаруживает исключение в коде. Если все данные удовлетворяют условиям кода, то блок с исключениями просто пропускается программой.
Подводим итоги
Как использовать try , catch и throw в С++, мы разобрались. Теперь кратко напомним, зачем все это нужно:
- В первую очередь, код с try / catch занимает меньше строк и легче читается. Блоков с операторами if / else может быть очень много и они будут повторяться, тогда как try / catch содержит только два блока.
- Исключения C++ заставляют код определять условия ошибки и обрабатывать исключения. Это позволяет не останавливать выполнение программы.
- После обнаружения исключения код на C++ перестает считывать объекты в блоке кода, сокращая использование программных ресурсов.
- Получение понятного сообщения об ошибке сильно упрощает процесс исправления бага. Особенно это полезно в случае, если исключение выбрасывается не из-за написанного кода, а, к примеру, из-за использованной библиотеки.
- Типы ошибок в C++ можно группировать вместе, что позволяет создавать иерархию объектов исключений, группировать их по именам, классам и категоризировать по виду.
Видео: С++ try catch. Обработка исключений С++. Try catch: что это. Изучение С++ для начинающих