18.1.2. Обработка исключения
Объявление исключения (exception declaration) в директиве catch (catch clause) выглядит как список параметров функции, только с одним параметром. Как и в списке параметров, имя параметра обработчика можно пропустить, если у блока catch нет необходимости в доступе к переданному исключению.
Тип объявления определяет виды исключений, обрабатываемых обработчиком. Тип должен быть завершенным (см. раздел 7.3.3). Тип может быть ссылкой на l-значение, но не ссылкой на r-значение (см. раздел 13.6.1).
При входе в блок catch параметр в объявлении исключения инициализируется объектом исключения. Подобно параметру функции, если тип параметра обработчика не является ссылочным, параметр обработчика копирует объект исключения; изменения, внесенные в параметр в обработчике, осуществляются с его локальной копией, а не с самим объектом исключения. Если параметр имеет ссылочный тип, то, как любой ссылочный параметр, параметр обработчика будет только другим именем объекта исключения. Изменения, внесенные в ссылочный параметр, осуществляются с самим объектом исключения.
Подобно объявлению параметра функции, параметр обработчика, имеющий тип базового класса, может быть инициализирован объектом исключения типа производного класса. Если у параметра обработчика будет не ссылочный тип, то объект исключения будет отсечен (см. раздел 15.2.3), как и при передаче такого объекта обычной функции по значению. С другой стороны, если параметр является ссылкой на тип базового класса, то параметр будет связан с объектом исключения обычным способом.
Также, подобно параметрам функции, статический тип объявления исключения определяет действия, которые может выполнить обработчик. Если у параметра обработчика будет тип базового класса, то обработчик не сможет использовать члены, определенные в производном классе.
Обычно обработчики, получающие исключения типа, связанного наследственными отношениями, определяют свой параметр как ссылку.
Поиск соответствующего обработчика
Блок catch, найденный в ходе поиска соответствующего обработчика, не обязательно является наиболее подходящим данному исключению. В результате исключение будет обработано первым найденным блоком catch, который сможет это сделать. Как следствие, в списке директив catch наиболее специализированные обработчики следует располагать в начале.
Поскольку поиск директивы catch осуществляется в порядке их объявления, при использовании исключений из иерархии наследования блоки catch для обработки исключений производного типа следует располагать перед обработчиком для исключения базового типа.
Правила поиска соответствующего исключению блока catch значительно жестче, чем правила поиска аргументов, соответствующих типам параметров. Большинство преобразований здесь недопустимо — тип исключения должен точно соответствовать обработчику, допустимо лишь несколько различий.
• Допустимо преобразование из неконстантного типа в константный, т.е. переданный неконстантный объект исключения может быть обработан блоком catch, ожидающим ссылку на константный.
• Допустимо преобразование из производного типа в базовый.
• Массив преобразуется в указатель на тип массива; функция преобразуется в соответствующий указатель на тип функции.
Никакие другие преобразования при поиске соответствующего обработчика недопустимы. В частности, невозможны ни стандартные арифметические преобразования, ни преобразования, определенные для классов.
В наборе директив catch с типами, связанными наследованием, обработчики для более производных типов следует располагать прежде наименее производных.
Повторная передача исключения
Вполне возможна ситуация, когда один блок кода catch (обработчик) не сможет полностью обработать исключение. После некоторых корректирующих действий обработчик может решать, что это исключение следует обработать в функции, которая расположена далее по цепи вызовов. Обработчик может передавать исключение другому, внешнему обработчику, который принадлежит функции, вызвавшей данную. Это называется повторной передачей исключения (rethrow). Повторную передачу осуществляет оператор throw, после которого нет ни имени типа, ни выражения.
throw;
Пустой оператор throw может присутствовать только в обработчике или в функции, вызов которой осуществляется из обработчика (прямо или косвенно). Если пустой оператор throw встретится вне обработчика, будет вызвана функция terminate().
Повторная передача не определяет нового исключения; по цепочке передается текущий объект исключения.
Обычно обработчик вполне может изменить содержимое своего параметра. Если после изменения своего параметра обработчик повторно передаст исключение, то эти изменения будут переданы далее, только если параметр обработчика объявлен как ссылка:
catch (my_error &eObj) { // спецификатор ссылочного типа
eObj.status = errCodes::severeErr; // изменение объекта исключения
throw; // переменная-член status объекта исключения имеет
// значение severeErr
} catch (other_error eObj) { // спецификатор нессылочного типа
eObj.status = errCodes::badErr; // изменение только локальной копии
throw; // значение переменной-члена status объекта исключения
// при повторной передаче не изменилось
}
Обработчик для всех исключений
Иногда необходимо обрабатывать все исключения, которые могут произойти, независимо от их типа. Обработка каждого возможного исключения может быть проблематична: иногда неизвестно, исключения каких типов могут быть переданы. Даже когда все возможные типы известны, предоставление отдельной директивы catch для каждого возможного исключения может оказаться весьма утомительным. Для обработки всех исключений в объявлении исключения используется многоточие. Такие обработчики, называемые обработчиками для всех исключений (catch-all), имеют форму catch(...). Такая директива соответствует исключениям любого типа.
Обработчик catch(...) зачастую используется в комбинации с выражением повторной передачи. Обработчик осуществляет все локальные действия, а затем повторно передает исключение:
void manip() {
try {
// действия, приводящие к передаче исключения
} catch (...) {
// действия по частичной обработке исключения
throw;
}
Директива catch(...) применяется самостоятельно или в составе нескольких директив catch.
Если директива catch(...) используется в комбинации с другими, она должна располагаться последней. Любой обработчик, следующий за обработчиком для всех исключений, никогда не будет выполнен.
Упражнения раздела 18.1.2
Упражнение 18.4. Заглянув вперед в иерархию наследования на рис. 18.1, объясните, что неправильно в следующем блоке try. Исправьте его:
try {
// использовать стандартную библиотеку С++
} catch(exception) {
// ...
} catch(const runtime_error &re) {
// ...
} catch(overflow_error eobj) { /* ... */ }
Упражнение 18.5. Измените следующую функцию main() так, чтобы обрабатывались исключения любых типов, представленных на рис. 18.1:
int main() {
// использовать стандартную библиотеку С++
}
Обработчики должны выводить сообщения об ошибках, связанных с исключением, прежде, чем вызывать функцию abort() (определенную в заголовке cstdlib) для завершения функции main().
Упражнение 18.6. С учетом следующих типов исключений и директивы catch напишите выражение throw, создающее объект исключения, который может быть обработан каждым блоком catch:
(a) class exceptionType { };
catch (exceptionType *pet) { }
(b) catch (...) { }
(c) typedef int EXCPTYPE;
catch (EXCPTYPE) { }
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОК