Язык программирования C++

Примеры обработки исключительных ситуаций


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

Прежде всего, имеет смысл определить для них специальный класс. Простейшим вариантом является класс, который может хранить код ошибки:

class Exception { public : enum ErrorCode { NO_MEMORY, DATABASE_ERROR, INTERNAL_ERROR, ILLEGAL_VALUE }; Exception(ErrorCode errorKind, const StringerrMessage); ErrorCode GetErrorKind(void )const {return kind;}; const StringGetErrorMessage(void )const {return msg;}; private : ErrorCode kind; String msg; };

Создание исключительной ситуации будет выглядеть следующим образом:

if (connect(serverName)==false ) throw Exception(Exception::DATABASE_ERROR, serverName);

А проверка на исключительную ситуацию так:

try { ... }catch (Exceptione){ cerr "Произошла ошибка "e.GetErrorKind() "Дополнительная информация:" e.GetErrorMessage(); }

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

throw AnotherException;

то блок catch будет пропущен: он ожидает только исключительных ситуаций типа Exception . Это особенно существенно при сопряжении нескольких различных программ и библиотек – каждый набор классов отвечает только за собственные ошибки.

В данном случае код ошибки записывается в объекте типа Exception . Если в одном блоке catch ожидается несколько разных исключительных ситуаций, и для них необходима разная обработка, то в программе придется анализировать код ошибки с помощью операторов if или switch .

try { ... }catch (Exceptione){ cerr "Произошла ошибка "e.GetErrorKind() "Дополнительная информация:" e.GetErrorMessage(); if (e.GetErrorKind()==Exception::NO_MEMORY || e.GetErrorKind()== Exception::INTERNAL_ERROR) throw ; else if (e.GetErrorKind()== Exception::DATABASE_ERROR) return TRY_AGAIN; else if (e.GetErrorKind()== Exception::ILLEGAL_VALUE) return NEXT_VALUE; }


Другим методом разделения различных исключительных ситуаций является создание иерархии классов – по классу на каждый тип исключительной ситуации.

16.1.  Пример иерархии классов для представления исключительных ситуаций.
В приведенной на рисунке 16.1 структуре классов все исключительные ситуации делятся на ситуации, связанные с работой базы данных (класс DatabaseException ), и внутренние ошибки программы (класс InternalException ). В свою очередь, ошибки базы данных бывают двух типов: ошибки соединения (представленные классом ConnectDbException ) и ошибки чтения (ReadDbException ). Внутренние исключительные ситуации и разделены на нехватку памяти (NoMemoryException )и недопустимые значения (IllegalValException ).
Теперь блок catch может быть записан в следующем виде:
try { }catch (ConnectDbExceptione ){ //обработка ошибки соединения с базой данных }catch (ReadDbExceptione){ //обработка ошибок чтения из базы данных }catch (DatabaseExceptione){ //обработка других ошибок базы данных }catch (NoMemoryExceptione){ //обработка нехватки памяти }catch (…){ //обработка всех остальных исключительных //ситуаций }
Напомним, что когда при проверке исключительной ситуации на соответствие аргументу оператора catch проверка идет последовательно до тех пор, пока не найдется подходящий тип. Поэтому, например, нельзя ставить catch для класса DatabaseException впереди catch для класса ConnectDbException – исключительная ситуация типа ConnectDbException совместима с классом DatabaseException (это ее базовый класс), и она будет обработана в catch для DatabaseException и не дойдет до блока с ConnectDbException .
Построение системы классов для разных исключительных ситуаций на стадии описания ошибок – процесс более трудоемкий, приходится создавать новый класс для каждого типа исключительной ситуации. Однако с точки зрения обработки он более гибкий и позволяет писать более простые программы.
Чтобы облегчить обработку ошибок и сделать запись о них более наглядной, описания методов и функций можно дополнить информацией, какого типа исключительные ситуации они могут создавать:


class Database { public : Open(const char*serverName) throw ConnectDbException; };
Такое описание говорит о том, что метод Open класса Database может создать исключительную ситуацию типа ConnectDbException . Соответственно, при использовании этого метода желательно предусмотреть обработку возможной исключительной ситуации.
В заключение приведем несколько рекомендаций по использованию исключительных ситуаций.
    При возникновении исключительной ситуации остаток функции или метода не выполняется. Более того, при обработке ее не всегда известно, где именно возникла исключительная ситуация. Поэтому прежде чем выполнить оператор throw , освободите ресурсы, зарезервированные в текущей функции. Например, если какой-либо объект был создан с помощью new , необходимо явно вызвать для него delete .Избегайте использования исключительных ситуаций в деструкторах. Деструктор может быть вызван в результате уже возникшей исключительной ситуации при откате вызовов функций и методов. Повторная исключительная ситуация не обрабатывается и завершает выполнение программы.

Если исключительная ситуация возникла в конструкторе объекта, считается, что объект сформирован не полностью, и деструктор для него вызван не будет.
Содержание раздела