обработка исключений. Типичная функция, написанная на С, выглядит примерно так
![]()
|
Язык С представляет программисту очень ограниченные возможности обработки исключений, возникших при работе программы. В этом отношении С++ намного развитее С. Здесь у программиста существенно большие возможности по непосредственной обработке исключений. Комитет по разработке стандартов С++ предоставил очень простую, но мощную форму обработки исключений. Типичная функция, написанная на С, выглядит примерно так: long DoSomething() { long *a, c; FILE *b; a = malloc(sizeof(long) * 10); if (a == NULL) return 1; b = fopen("something.bah", "rb"); if (b == NULL) { free(a); return 2; } fread(a, sizeof(long), 10, b); if (a[0] != 0x10) { free(a); fclose(b); return 3; } fclose(b); c = a[1]; free(a); return c; } Выглядит не очень, не так ли? Вы целиком и полностью зависите от значений, которые возвращают вам функции и для каждой ошибки вам постоянно нужен код, который ее обрабатывает. Если вы, скажем, в функции работаете хотя бы с 10 указателями (рапределяете память, освобождаете ее и т.д.), то наверняка половину кода функции будет занимать код обработки ошибок. Такая же ситуация будет в коде, вызывающем эту функцию, так как здесь также нужно обработать все возвращаемые коды ошибок. Try-catch-throw Давайте же разберем основы обработки исключений в С++. Чтобы комфортно работать с исключениями в С++ вам нужно знать лишь три ключевых слова: try (пытаться) - начало блока исключений; catch (поймать) - начало блока, "ловящего" исключение; throw (бросить) - ключевое слово, "создающее" ("возбуждающее") исключение. блок try-catchПростейший формат защищенного блока имеет вид: try {операторы защищенного блока} catch(...) {обработчик ошибочной ситуации} А теперь пример, демонстрирующий, как применить то, что вы узнали: void func() { try { throw 1; } catch(int a) { cout << "Caught exception number: " << a << endl; return; } cout << "No exception detected!" << endl; return; } Если выполнить этот фрагмент кода, то мы получим следующий результат: Caught exception number: 1 Теперь закоментируйте строку throw 1; и функция выдаст такой результат: No exception detected! Для их возбуждения используется оператор throw. Тип выражения, указанного в операторе throw, определяет тип исключительной ситуации, а значение может быть передано обработчику прерываний. Этот механизм, заявленный как стандартный, представляется весьма экзотическим без использования механизма классов. Соответственно, полный формат защищенного блока имеет вид: try {операторы защищенного блока} {catch-блоки}… Catch-блок имеет один из следующих форматов: catch (тип) {обработчик ошибочной ситуации} catch (тип идентификатор) {обработчик ошибочной ситуации} catch (…) {обработчик ошибочной ситуации} Первый формат используется, если нам надо указать тип перехватываемого исключения, но не нужно обрабатывать связанное с этим исключением значение (это достигается при использовании второго формата оператора catch). Наконец, третий формат оператора catch позволяет обработать все исключения. Обработка исключений, возбужденных оператором throw, идет по следующей схеме: 1. Создается статическая переменная со значением, заданным в операторе throw. Она будет существовать до тех пор, пока исключение не будет обработано. Если переменная-исключение является объектом класса, при ее создании работает конструктор копирования. 2. Завершается выполнение защищенного try-блока: раскручивается стек подпрограмм, вызываются деструкторы для тех объектов, время жизни которых истекает и т.д. 3. Выполняется поиск первого из catch-блоков, который пригоден для обработки созданного исключения. Поиск ведется по следующим критериям: — если тип, указанный в catch-блоке, совпадает с типом созданного исключения, или является ссылкой на этот тип; — класс, заданный в catch-блоке, является предком класса, заданного в throw, и наследование выполнялось с ключом доступа public; — указатель, заданный в операторе throw, может быть преобразован по стандартным правилам к указателю, заданному в catch-блоке. — в операторе throw задано многоточие. Если нужный обработчик найден, то ему передается управление и, при необходимости, значение оператора throw. Оставшиеся catch-блоки, относящиеся к защищенному блоку, в котором было создано исключение, игнорируются. Из указанных правил поиска следует, что очень важен порядок расположения catch-блоков. Так, блок catch(…) должен стоять последним в списке, а блок catch (void *) – после всех блоков с указательными типами. Если ни один из catch-блоков, указанных после защищенного блока, не сработал, то исключение считается необработанным. Его обработка может быть продолжена во внешних блоках try (если они, конечно, есть). В конце оператора catch может стоять оператор throw без параметров. В этом случае работа catch-блока считается незавершенной а исключение – не обработанным до конца, и происходит поиск соответствующего обработчика на более высоких уровнях. Если оператор throw был вызван вне защищенного блока (что чаще всего случается, когда исключение возбуждается в вызванной функции), или если не был найден ни один подходящий обработчик этого исключения, то вызывается стандартная функция terminate(). Она, в свою очередь, вызывает функциюabort() для завершения работы с приложением. Единственное, что доступно программисту в этом случае – зарегистрировать с помощью функции set_terminate свою функцию, которая будет выполняться перед аварийным завершением работы. Например: void MyTerminate() { std::cout << "An error occured!" << std::endl; exit(-1); } int main () { set_terminate(MyTerminate); throw 0; Как видите все очень просто, но если это применить с умом, такой подход покажется вам очень мощным средством обработки ошибок. Catch может "ловить" любой тип данных, так же как и throw может "кинуть" данные любого типа. Т.е. throw AnyClass(); будет правильно работать, так же как и catch (AnyClass &d) {};. Как уже было сказано, catch может "ловить" данные любого типа, но вовсе не обязательно при это указывать переменную. Т.е. прекрасно будет работать что-нибудь типа этого: catch(dumbclass) { } так же, как и catch(dumbclass&) { } Так же можно "поймать" и все исключения: catch(...) { } Троеточие в этом случае показывает, что будут пойманы все исключения. При таком подходе нельзя указать имя переменной. В случае, если "кидаются" данные нестандартного типа (экземпляры определенных вами классов, структур и т.д.), лучше "ловить" их по ссылке, иначе вся "кидаемая" переменная будет скопирована в стек вместо того, чтобы просто передать указатель на нее. Если кидаются данные нескольких типов и вы хотите поймать конкретную переменную (вернее, переменную конкретного типа), то можно использовать несколько блоков catch, ловящих "свой" тип данных: try { throw 1; // throw 'a'; } catch (long b) { cout << "пойман тип long: " << b << endl; } catch (char b) { cout << "пойман тип char: " << b << endl; }" Создание" исключений Когда возбуждается исключительная ситуация, программа просматривает стек функций до тех пор, пока не находит соответствующий catch. Если оператор catch не найден, STL будет обрабатывать исключение в стандартном обработчике, который делает все менее изящно, чем могли бы сделать вы, показывая какие-то непонятные (для конечного пользователя) сообщения и обычно аварийно завершая программу. Однако более важным моментом является то, что пока просматривается стек функций, вызываются деструкторы всех локальных классов, так что вам не нужно забодиться об освобождении памяти и т.п. |