Кое-что об исключениях в C++

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

вступление.

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

блок
try-catch

Простейший формат защищенного блока имеет вид:

try {операторы защищенного блока}
catch(...) {обработчик ошибочной ситуации}


Важно! Многоточие является частью синтаксиса языка!
Работает эта конструкция так.
Выполняются инструкции, входящие в состав блока
try
(защищенный блок). Если при их выполнении исключение не возбуждается (в C++ чаще используется термин «выброс исключения»), то блок
catch 
пропускается. При выбросе исключения выполнение защищенного блока прекращается, и начинают работать инструкции, записанные в блоке
catch
.
Основной смысл этих инструкций – корректная обработка исключительной ситуации. Кроме того, в блок
catch
имеет смысл поместить код, который освобождает ресурсы, захваченные выполнившимися инструкциями из блока
try
. После окончания работы блока
catch
исключение считается обработанным, и управление передается на первую инструкцию, следующую за конструкцией
try … catch
.

Пример: перехват системного исключения
"деление на ноль"

int x = 0;
try {
std::cout <<2/x; //Здесь произойдет выброс исключения
// Последующие операторы выполняться не будут
}
catch (...) {
std::cout << "Division by zero" << std::endl;


Вот и все, и совсем не больно)

throw

Гораздо более интересным является механизм создания собственных исключений. Для их возбуждения используется оператор
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;
}

Преимущества и недостатки работы с исключениями


Возбуждение исключения не является единственным способом сообщить о возникновении
внештатной ситуации в ходе выполнения программы. Просигнализировать о возникшей ошибке
могут и другие механизмы, например:
— занесение информации о состоянии программы в специальную переменную или поле класса и использование специальных механизмов доступа к этой информации;
— задание таких спецификаций для функций, которые сигнализировали бы о наступлении
нештатной ситуации (например, использование булевских функций, которые возвращали бы
значение
false
при возникновении таких ситуаций);
— использование стандартных функций, сообщающих об ошибках, например
perror()
.
Выбор того или иного механизма реакции на нештатные ситуации достаточно сложен, поскольку
четких критериев не существует, да и не может существовать. Мы должны учитывать следующие соображения:
— игнорирование исключения приводит к аварийному завершению приложения,
а игнорирование другой информации проходит незаметно (хотя результаты могут быть и непредсказуемыми);
— если в программе есть большое число вызовов функции, которая может создать
нештатную ситуацию, то логичнее было бы включить их в один защищенный блок, нежели
выполнять проверку результатов для каждого вызова;
— с другой стороны, следует иметь в виду, что после возникновения исключительной ситуации и ее обработки работа защищенного блока не возобновляется.

Надеюсь, моя статья кому-нибудь помогла.


0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.