С++ для начинающих


Перехват всех исключений


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

void manip() {

   resource res;

   res.lock();       // захват ресурса

   // использование ресурса

   // действие, в результате которого возбуждено исключение

   res.release();    // не выполняется, если возбуждено исключение

}

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

// управление попадает сюда при любом возбужденном исключении

catch (...) {

   // здесь размещаем наш код

}



Конструкция catch(...) используется в сочетании с повторным возбуждением исключения. Захваченный ресурс освобождается внутри составной инструкции в catch-обработчике перед тем, как передать исключение по цепочке вложенных вызовов в результате повторного возбуждения:

void manip() {

   resource res;

   res.lock();

   try {

      // использование ресурса

      // действие, в результате которого возбуждено исключение

   }

   catch (...) {

      res.release();

      throw;

   }

   res.release();   // не выполняется, если возбуждено исключение

}

Чтобы гарантировать освобождение ресурса в случае, когда выход из manip() происходит в результате исключения, мы освобождаем его внутри catch(...) до того, как исключение будет передано дальше. Можно также управлять захватом и освобождением ресурса путем инкапсуляции в класс всей работы с ним. Тогда захват будет реализован в конструкторе, а освобождение – в автоматически вызываемом деструкторе. (С этим подходом мы познакомимся в главе 19.)


Предложение catch(...) используется самостоятельно или в сочетании с другими catch-обработчиками. В последнем случае следует позаботиться о правильной организации обработчиков, ассоциированных с try-блоком.

Catch-обработчики исследуются по очереди, в том порядке, в котором они записаны. Как только найден подходящий, просмотр прекращается. Следовательно, если предложение catch(...) употребляется вместе с другими catch-обработчиками, то оно должно быть последним в списке, иначе компилятор выдаст сообщение об ошибке:

try {

   stack.display();

   for ( int ix = 1; ix < 51; ++x )

   {

      // то же, что и выше

   }

}

catch ( pushOnFull ) { }

catch ( popOnEmpty ) { }

catch ( ... ) { }   // должно быть последним в списке catch-обработчиков

Упражнение 11.4

Объясните, почему модель обработки исключений в C++ называется невозвратной.

Упражнение 11.5

Даны следующие объявления исключений. Напишите выражения throw, создающие объект-исключение, который может быть перехвачен указанными обработчиками:

(a) class exceptionType { };

    catch( exceptionType *pet ) { }

(b) catch(...) { }

(c) enum mathErr { overflow, underflow, zeroDivide };

    catch( mathErr &ref ) { }

(d) typedef int EXCPTYPE;

    catch( EXCPTYPE ) { }

Упражнение 11.6

Объясните, что происходит во время раскрутки стека.

Упражнение 11.7

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

Упражнение 11.8

На основе кода, написанного вами в упражнении 11.3, модифицируйте класс созданного исключения: неправильный индекс, использованный в операторе operator[](), должен сохраняться в объекте-исключении и затем выводиться catch-обработчиком. Измените программу так, чтобы operator[]() возбуждал при ее выполнении исключение.


Содержание раздела