А.В. Столяров - Введение в язык Си++ (1114949), страница 17
Текст из файла (страница 17)
Мы могли бы «бросить»выражение другого типа; например, следующий оператор «бросает» исключение типа int:throw 27;2Строго говоря, это не совсем так, поскольку есть ещё, например, конструкторыи деструкторы глобальных объектов, которые отрабатывают, соответственно, до ипосле функции main О, но в некотором приближении можно этим пренебречь.75Чаще, однако, в роли исключений выступают объекты специально введённых для этой цели классов; речь об этом пойдёт впереди.§ 3.4. О бработка исклю ченийОбработку исключительных ситуаций следует предусматривать, естественно, только в тех местах, в которых мы можем с соответствующейситуацией справиться. Так, если дальнейшая работа после возникновенияисключительной ситуации требует обращения к пользователю, то обработку такой ситуации следует вставить в одну из функций, наделённыхправом вести диалог с пользователем (возможно, в функции main).Общий принцип организации обработки исключений таков.
Из программы выделяется некоторый блок кода, исполнение которого, предположительно, может привести к возникновению исключительных ситуаций. Этот блок явно выделяется и снабжается одной или несколькимиинструкциями о том, как необходимо действовать при возникновении исключения того или иного типа.
При этом такие инструкции действуютв отношении исключений соответствующих типов, возникш их в програм м е с м ом ента входа в помеченный блок и до мом ента вы хода из него, в том числе в функциях, вызванных из блока прямо иликосвенно. Глубина вызовов в данном случае роли не играет.Для пометки блока с целью обработки исключительных ситуаций вС и + + используется ключевое слово try (англ, п о п ы т а т ь с я ) , за которым следует собственно блок, то есть последовательность операторов,заключенная в фигурные скобки. Сразу после tr y -блока необходимо поместить один или больше обработчи ков исключений.
Обработчик исключений синтаксически несколько напоминает функцию с одним параметром, хотя это только внешнее сходство; на самом деле, обработчикначинается с ключевого слова catch (англ, п о й м а т ь ) , сразу после негоставятся круглые скобки, внутри которых — описание формального параметра (точно так, как это делается при описании функции с одним параметром). Тип параметра задаёт тип обрабатываемого исключения, а поимени параметра можно получить доступ к значению исключения. Дляпримера припомним функцию из предыдущего параграфа, подсчитывающую количество строк в файле, и на основе этой функции напишемпрограмму целиком. Будем считать, что функция line_count_in_f i l eнаходится в отдельном модуле, так что в программе нам потребуетсятолько её прототип.#include <stdio.h >unsigned in t lin e _ co u n t_ in _ file (co n st char *file _ n a m e );76in t m ain(int argc, char **a rg v ) {if(a rg c < 2 ) {f p r in t f ( s t d e r r , "No f i l e nam e\n");return 1;}try {in t re s = lin e _ c o u n t_ in _ file (a r g v [l]) ;p rin tf("T h e f i l e %s contains %d lin e s \ n " ,a r g v [l], r e s);}catch(const char *exception) {f p r in t f ( s t d e r r , "Exception ( s t r in g ) : % s\n",e x cep tio n );return 1;}return 0;}Эта программа, если всё будет в порядке, напечатает имя файла иколичество строк в нём; если же открыть файл не удастся, функцияline_count_in_f i l e переведёт программу в состояние исключительнойситуации, а соответствующий обработчик будет найден в функции main.Ни остаток функции line_count_in_f i l e , ни остаток tr y -блока в этомслучае выполняться не будут, управление окажется сразу передано в обработчик (catch-блок).
В этом примере исключение может возникнутьв функции, непосредственно вызванной из try -блока, но на самом деле это не важно: с таким же успехом оно могло возникнуть в функции,вызванной из line_count_in_f i l e , и т. д. на любую глубину.Обратите внимание, что мы в данном случае «ловим» исключениеconst char *, а не просто char *, поскольку именно такой тип (указатель на константу типа char) имеет в Си и С и + + строковый литерал — строка, заключённая в двойные кавычки. Если убрать модификатор const, исключение поймано не будет.В рассмотренном примере мы предусмотрели всего один обработчикисключения, хотя язык С и + + допускает произвольное их количество (неменее одного). Если бы из нашего try -блока вызывалось больше функцийи потенциально они могли бы привести к исключительным ситуациямдругих типов, мы могли бы написать что-то вроде следующего:try {И ...}catch(const char *х) {и ...77}ca tch (in t x) {// ...}Здесь важно понимать несколько простых правил:• О бработчики (catch-блоки) рассм атри ваю тся по порядку,один з а другим , причём сработать м ож ет только один изних, или ни одного.
Это значит, в частности, что писать дваобработчика одного типа или типов, сводимых один к другому3,нет никакого смысла.• О бработчик м ож ет пойм ать только исклю чение, возникш ее во врем я работы соответствую щ его tr y -блока. В частности, обработчик не может поймать исключение, которое выбросилодин из предыдущих обработчиков.• Если исклю чение не поймано ни одним из обработчиков,оно «вы б р асы вается» д ал ьш е, к ак если бы ф рагм ен т кода,в котором исклю чение возникло, вовсе не был обрамлённикаким tr y -блоком.
Таким образом, на пути одного исключенияможет оказаться несколько try -блоков, как бы вложенных друг вдруга (во всяком случае, в смысле временных периодов исполнения), и в некоторых из них может не оказаться подходящего обработчика. В таком случае досрочное завершение активных функцийи уничтожение стековых фреймов продолжается дальше, пока небудет найден try -блок с подходящим обработчиком.В обработчиках исключений можно использовать специальную форму оператора throw, а именно — написать этот оператор без параметров.Это означает «бросить то исключение, которое только что было поймано». Так делают в случаях, когда полностью справиться с возникшей ситуацией в данном месте программы невозможно, но прежде чем броситьисключение дальше, нужно произвести ещё какие-то действия.
Примерытаких ситуаций рассматриваются в следующем параграфе.§3.5. О бработчики с м ноготочиемВ некоторых случаях оказывается осмысленным обработчик, способный поймать произвольное исключение независимо о т его типа. Такой3Правила преобразования типов при обработке исключений несколько отличаютсяот правил, применяемых при вызове функций; мы рассмотрим эти правила в одномиз следующих параграфов.78обработчик записывается так же, как и обычный, только вместо типаисключения и имени параметра в скобках указывается многоточие.
Конечно, значение исключения в этом случае использовано быть не может,но в некоторых случаях это и не важно. Например, если мы подключаемк нашей программе модули, написанные другими программистами, и неуверены в том, что нам известны все типы исключений, генерируемыетакими модулями, может быть неплохой идея расположить в функцииmain примерно такой try -блок:in t main() {try {/ / здесь пишем весь код главной функцииreturn 0;}catch(const char *х) {f p r in t f ( s t d e r r , "Exception ( s t r in g ) : % s\n", x ) ;}ca tch (in t x) {f p r in t f ( s t d e r r , "Exception ( in t ) : %d\n", x ) ;}c a tc h (. .
. ) {f p r in t f ( s t d e r r , "Something stran ge cau gh t\n ");}return 1;}Обработчики с многоточием делают особенно актуальной форму throwбез параметров. Использование в таких обработчиках оператора throwбез параметров позволяет поймать исключение произвольного типа, выполнить какие-то действия, после чего бросить исключение дальше.
Этоможет оказаться полезным, если в нашем коде локально захватываютсяте или иные ресурсы (выделяется память, открываются файлы и т. и.) иих следует освободить, прежде чем функция окажется завершена. Рассмотрим для примера функцию, в которой выделяется динамическиймассив целых чисел:void f ( i n t n) {in t *p = new in t [n] ;/ / весь остальной код функцииd e le te [] р;}Если во время выполнения кода, находящегося между new и d elete , возникнет исключение, то d elete не будет выполнен и массив, на которыйуказывал р, будет продолжать занимать память (то есть станет мусором).79Проблема снимается, если весь этот код заключить в try -блок, после которого есть обработчик для исключений произвольного типа, который,прежде чем бросить исключение дальше, освобождает память:void f ( i n t n) {in t *p = new in t [ n ] ;tr y {/ / весь остальной код функции}catch ( .
. . ) {d e le te [] p;throw;}d e le te [] p;}§ 3.6. О бъект к л асс а в р оли и склю чен и яОбычно при возникновении исключительной ситуации возникает желание передать обработчику максимум информации о возникших трудностях. Встроенные типы данных плохо пригодны для этого. Действительно, в рассмотренном выше примере было бы неплохо передать обработчику не только текстовое сообщение «couldn’t open the file», но иимя файла, и значение переменной errno сразу после выполнения fopen,которое может помочь в анализе проблемы.