С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 91
Текст из файла (страница 91)
Например:}В этом примере объект state не используется в качестве объекта-исключения. Вместоэтого выражением throw создается объект-исключение типа EHstate, которыйинициализируется значением глобального объекта state. Как программа можетразличить их? Для ответа на этот вопрос мы должны присмотреться к объявлениюисключения в catch-обработчике более внимательно.Это объявление ведет себя почти так же, как объявление формального параметра. Еслипри входе в catch-обработчик исключения выясняется, что в нем объявлен объект, то онинициализируется копией объекта-исключения. Например, следующая функция530С++ для начинающихcalculate() вызывает определенную выше mathFunc(). При входе в catch-обработчиквнутри calculate() объект eObj инициализируется копией объекта-исключения,void calculate( int op ) {try {mathFunc( op );}catch ( EHstate eObj ) {// eObj - копия сгенерированного объекта-исключения}созданного выражением throw.}Объявление исключения в этом примере напоминает передачу параметра по значению.Объект eObj инициализируется значением объекта-исключения точно так же, какпереданный по значению формальный параметр функции – значением соответствующегофактического аргумента.
(Передача параметров по значению рассматривалась в разделе7.3.)Как и в случае параметров функции, в объявлении исключения может фигурироватьссылка. Тогда catch-обработчик будет напрямую ссылаться на объект-исключение,void calculate( int op ) {try {mathFunc( op );}catch ( EHstate &eObj ) {// eObj ссылается на сгенерированный объект-исключение}сгенерированный выражением throw, а не создавать его локальную копию:}Для предотвращения ненужного копирования больших объектов применять ссылкиследует не только в объявлениях параметров типа класса, но и в объявлениях исключенийтого же типа.В последнем случае catch-обработчик сможет модифицировать объект-исключение.Однако переменные, определенные в выражении throw, остаются без изменения.Например, модификация eObj внутри catch-обработчика не затрагивает глобальнуюvoid calculate( int op ) {try {mathFunc( op );}catch ( EHstate &eObj ) {// исправить ошибку, вызвавшую исключениеeObj = noErr; // глобальная переменная state не изменилась}переменную state, установленную в выражении throw:}531С++ для начинающихCatch-обработчик переустанавливает eObj в noErr после исправления ошибки,вызвавшей исключение.
Поскольку eObj – это ссылка, можно ожидать, что присваиваниемодифицирует глобальную переменную state. Однако изменяется лишь объектисключение, созданный в выражении throw, поэтому модификация eObj не затрагиваетstate.11.3.2. Раскрутка стекаПоиск catch-обработчикадля возбужденного исключения происходит следующимобразом. Когда выражение throw находится в try-блоке, все ассоциированные с нимпредложения catch исследуются с точки зрения того, могут ли они обработатьисключение. Если подходящее предложение catch найдено, то исключениеобрабатывается. В противном случае поиск продолжается в вызывающей функции.Предположим, что вызов функции, выполнение которой прекратилось в результатеисключения, погружен в try-блок; в такой ситуации исследуются все предложения catch,ассоциированные с этим блоком.
Если один из них может обработать исключение, топроцесс заканчивается. В противном случае переходим к следующей по порядкувызывающей функции. Этот поиск последовательно проводится во всей цепочкевложенных вызовов. Как только будет найдено подходящее предложение, управлениепередается в соответствующий обработчик.В нашем примере первая функция, для которой нужен catch-обработчик, – это функциячлен pop() класса iStack.
Поскольку выражение throw внутри pop() не находится в tryблоке, то программа покидает pop(), не обработав исключение. Следующейрассматривается функция, вызвавшая pop(), то есть main(). Вызов pop() внутриmain() находится в try-блоке, и далее исследуется, может ли хотя бы одноассоциированное с ним предложение catch обработать исключение. Посколькуобработчик исключения popOnEmpty имеется, то управление попадает в него.Процесс, в результате которого программа последовательно покидает составныеинструкции и определения функций в поисках предложения catch, способногообработать возникшее исключение, называется раскруткой стека. По мере раскруткипрекращают существование локальные объекты, объявленные в составных инструкциях иопределениях функций, из которых произошел выход.
C++ гарантирует, что во времяописанного процесса вызываются деструкторы локальных объектов классов, хотя ониисчезают из-за возбужденного исключения. (Подробнее мы поговорим об этом в главе19.)Если в программе нет предложения catch, способного обработать исключение, оноостается необработанным. Но исключение – это настолько серьезная ошибка, чтопрограмма не может продолжать выполнение. Поэтому, если обработчик не найден,вызывается функция terminate() из стандартной библиотеки C++. По умолчаниюterminate() активизирует функцию abort(), которая аномально завершает программу.(В большинстве ситуаций вызов abort() оказывается вполне приемлемым решением.Однако иногда необходимо переопределить действия, выполняемые функциейterminate(). Как это сделать, рассказывается в книге [STROUSTRUP97].)Вы уже, наверное, заметили, что обработка исключений и вызов функции во многомпохожи.
Выражение throw ведет себя аналогично вызову, а предложение catch чем-тонапоминает определение функции. Основная разница между этими двумя механизмамизаключается в том, что информация, необходимая для вызова функции, доступна вовремя компиляции, а для обработки исключений – нет. Обработка исключений в C++требует языковой поддержки во время выполнения.
Например, для обычного вызова532С++ для начинающихфункции компилятору в точке активизации уже известно, какая из перегруженныхфункций будет вызвана. При обработке же исключения компилятор не знает, в какойфункции находится catch-обработчик и откуда возобновится выполнение программы.Функция terminate() предоставляет механизм времени выполнения, который извещаетпользователя о том, что подходящего обработчика не нашлось.11.3.3. Повторное возбуждение исключенияМожет оказаться так, что в одном предложении catch не удалось полностью обработатьисключение. Выполнив некоторые корректирующие действия, catch-обработчик можетрешить, что дальнейшую обработку следует поручить функции, расположенной “выше” вцепочке вызовов. Передать исключение другому catch-обработчику можно с помощьюповторного возбуждения исключения.
Для этой цели в языке предусмотрена конструкцияthrow;которая вновь генерирует объект-исключение. Повторное возбуждение возможно толькоcatch ( exception eObj ) {if ( canHandle( eObj ) )// обработать исключениеreturn;else// повторно возбудить исключение, чтобы его перехватил другой// catch-обработчикthrow;внутри составной инструкции, являющейся частью catch-обработчика:}При повторном возбуждении новый объект-исключение не создается.
Это имеет значение,если catch-обработчик модифицирует объект, прежде чем возбудить исключениеenum EHstate { noErr, zeroOp, negativeOp, severeError };void calculate( int op ) {try {// исключение, возбужденное mathFunc(), имеет значение zeroOpmathFunc( op );}catch ( EHstate eObj ) {// что-то исправить// пытаемся модифицировать объект-исключениеeObj = severeErr;// предполагалось, что повторно возбужденное исключение будет// иметь значение severeErrthrow;}повторно. В следующем фрагменте исходный объект-исключение не изменяется. Почему?}533С++ для начинающих534Так как eObj не является ссылкой, то catch-обработчик получает копию объектаисключения, так что любые модификации eObj относятся к локальной копии и неотражаются на исходном объекте-исключении, передаваемом при повторномвозбуждении. Таким образом, переданный далее объект по-прежнему имеет тип zeroOp.Чтобы модифицировать исходный объект-исключение, в объявлении исключения внутриcatch ( EHstate &eObj ) {// модифицируем объект-исключениеeObj = severeErr;// повторно возбужденное исключение имеет значение severeErrthrow;catch-обработчика должна фигурировать ссылка:}Теперь eObj ссылается на объект-исключение, созданный выражением throw, так что всеизменения относятся непосредственно к исходному объекту.
Поэтому при повторномвозбуждении исключения далее передается модифицированный объект.Таким образом, другая причина для объявления ссылки в catch-обработчике заключаетсяв том, что сделанные внутри обработчика модификации объекта-исключения в такомслучае будут видны при повторном возбуждении исключения. (Третья причина будетрассмотрена в разделе 19.2, где мы расскажем, как catch-обработчик вызываетвиртуальные функции класса.)11.3.4. Перехват всех исключенийИногда функции нужно выполнить определенное действие до того, как она завершитобработку исключения, даже несмотря на то, что обработать его она не может.