Обработка исключительных ситуаций на примере try…catch в С++. Начало знакомства

"Что такое исключения в С++ и как начать их осваивать?" — подобный вопрос может вертеться у новичка в голове. К сожалению, оттолкнуться для старта не у каждого получается быстро. Несмотря на то, что общее представление об исключениях в голове есть, их использовать — некоторые мы не можем.
  • Исключениямеханизм языков программирования, предназначенный для описания реакции программы на ошибки времени выполнения и другие возможные проблемы, которые могут возникнуть при выполнении программы и приводят к невозможности (бессмысленности) дальнейшей отработки программой её базового алгоритма.
Классические примеры возникновения нештатных ситуаций:

  • Деление на ноль
  • Ошибка при попытке считать данные с внешнего устройства
  • Исчерпание доступной памяти
Сейчас напишем простенькую программу с обработкой исключения.


Листинг #1 наглядно отображает, каким образом вызываются исключения: делается проверка на определённое условие и с помощью ключевого слова throw идёт посыл своебразного сигнала (обычного значения) в блок catch, предназначенного для обработки исключительных ситуаций. Сейчас сигналом служит обычное строковоое значение. В нашем случае вся обработка — это лишь вывод на экран информации о сработавшем исключении. Отдавать в throw можно хоть объект своего класса. Но и принимать в блок catch следует соответствующий отдаваемому тип. Блок catch очень напоминает функцию с одним параметром:


Если вы еще плохо знакомы с классами, то не стоит сейчас пытаться понять листинг #2. В этом листинге используется конвертирующий конструктор, а конструкторы — это само по себе отдельная тема. Если же вы знакомы с понятием конвертирующего конструктора, то пример должен быть для вас достаточно простым и, надеюсь, крайне понятным. Сам этот пример показан только для того, чтобы продемонстрировать, что подаётся в задаваемое исключение некоторое нечто любого типа: необязательно только строка или только целое число, а от объекта того типа уже идут пляски: блок обработки исключения принимает параметр, в этот параметр приходит поданное значение, и этот параметр можно использовать в своих интересах. Например, на основании наших значений, мы могли бы вывести диагностическое сообщение о произошедшей ошибке: если значение оказалось нулём, то это, согласно коду, должно обозначать, что x принял нулевое значение и после проверки на такое событие запустилось соответствующее такому развитию исключение; если же оказалось минус-единицей, то это должно обозначать, что x принял отрицательное значение. Мы могли бы использовать любое другое удобное нам значение для обозначение сигнала ошибки. Тут единственное важно, чтобы пишущий код не запутал себя и дргих какими-нибудь нелогичными закономерностями вида: если нулевое значение, то пусть в исключение пойдёт минус один, а если отрицательное, то пусть пойдёт ноль. Сейчас будет показан гораздо более понятный листинг:


  • Исключение в С++ может иметь любой тип, обычно они имеют тип класса
  • Блок catch очень похож на определение функции, но это не функция, это обработчик, а параметром этого обработчика обозначается тип сгенерированного исключения (тип того элемента, который мы подсовываем в throw
Как можно заметить по листингу #3 — с одним try может идти много обработчиков, т. е. много catch. Как уже было упомянуто, в обработчик можно подавать любой тип, и на основании поданного типа срабатывает соответствующий поданному блок обработки исключения. Если использовать несколько блоков обработки и подавать в каждый из них пользовательские классы, то это обычно оказывается очень удобно. Такой приём может быть удобно использовать, например, для отдельных функций: для одной функции один bad-класс, для второй другой bad-класс, для третьей — третий.

То, что показано в листинге #3 может реализовываться следующим образом:


Вся эта программа из листинга #3.1 включает в себя всего лишь один класс, внутри которого написан метод получения числового значения. По названию класса несложно понять, что нашим классом моделируется переменная-возраст, а поскольку возраст не может быть отрицательным и обычно имеет определённый лимит (для разных живых организмов разный), то ни отрицательное, ни слишком большое число не подходят для записи. Чтобы предупредить развитие событий, когда вовнутрь класса будет подаваться отрицательное или слишком большое число, использован механизм исключений. Во время получения значения происходит проверка, и если что-то идёт не как запланировалось, то бросается исключение на определённое событие. Для удобства обработки под каждый бросок исключения создан индивидуальный тому класс, благодаря чему мы получаем довольно удобный функционал: класс позволяет более гибко выстраивать необходимое поведение, а в само исключение бросается тип по одному из созданных этих классов. Тут важно понимать, что обработка необходимого должна происходить в блоке try: т. е. там и бросаются исключения и описывается, что должно произойти, если предпосылок для исключений не возникнет. Всё, что описано будет вне блока, сработает в штатном режиме, как если бы исключений и не было.


При броске исключений в работе с try…catch не всегда удобно использовать непосредственный тип значения для отслеживания в ловушку-обработчик. Если использовать тип не такой, какой подаётся в бросок исключения, то блок обработки исключения не задействуется:



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


Что ожирять код, что постоянно менять — дело неблагодарное, поэтому существуют более удобные альтернативы, виды которых вы будете встречать или уже успели. Одна из таких альтернатив (которую применять НЕ нужно) выглядит так:


Почему не рекомендуется? Некоторые уверяют, что даже надо. Всё очень просто — это не из языка С++, это расширение некоторого компилятора: в одном компиляторе работать будет, а в другом — нет, не будет компилироваться. Если вы напишете много кода, а потом возникнет необходимость смены компилятора, то может прийтись переписывать участки кода из-за отсутствия расширения в новопользуемом, например. Можно использовать или exception с пустыми скобками, или не использовать этот класс вообще. Если использовать класс с пустыми круглыми скобками, то мы не можем самостоятельно подать элемент информирования вовнутрь обработчика. Но если этот элемент прям жизненно необходим, то предпочтение надо отдать или logic_error, или runtime_error. Эти два "бойца" умеют принимать в свои конструкторы данные, поэтому мы в подаче на обработку исключения смело можем этим пользоваться.

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

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

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

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/uriy-schupak/win32-api-razrabotka-prilozheniy-dlya-windows/?lfrom=15589587

Последние комментарии

Яндекс.Метрика