<<
>>

Исключения

Как уже упоминалось в разделах 1.7 и 5.8, достаточно большая часть любой программы приходится на перехват и обработку ситуаций, при возникновении которых по каким-либо причинам нормальный процесс обработки нарушается (ввод некорректной информации, попытка читать из несуществующего файла, обнаружение ситуации «деление на нуль» и т.п.).

Использование для проектирования программы технологии ООП приводит к тому, что обычно возникновение некорректной ситуации фиксируется в одном месте (объекте) программы, а принятие решения по ней возможно лишь в другом. Для обработки таких ситуаций используют механизм исключений.

К сожалению, традиционно в С и C++ используются разные стандарты обработки исключений.

Механизм исключений C++. В языке C++ используются специальные операторы throw, try и catch. Первый - для генерации исключения, а два других - для организации его перехвата.

Генерация исключений выполняется в том месте программы, где обнаруживается исключительная ситуация.

Оператор throw имеет следующий синтаксис:

throw []();

где - тип (чаще класс) генерируемого значения; если тип не указан, то компилятор определяет его исходя из типа аргумента (обычно это один из встроенных типов); - одно или несколько выражений, значения которых будут использованы для инициализации генерируемого объекта.

Например:

1) throw (“неверный параметр”); /* генерирует исключение типа const char * с указанным в кавычках значением */

2) throw (221); /* генерирует исключение типа const int с указанным значением */

3) class Е { //класс исключения

public: int пит; // номер исключения

E(int п): пит(п){} // конструктор класса

}

throw Е(5); // генерирует исключение в виде объекта класса Е

Перехват и обработка исключений осуществляются с помощью конструкции try ...

catch .. .(catch...):

try {Защищенный код>}

catch (){}

Блок операторов try содержит операторы, при выполнении которых могут возникнуть исключительные ситуации.'Блоков catch может быть несколько. Каждый блок catch включает операторы, которые должны быть выполнены, если при выполнении операторов блока try были зафиксировано исключение типа, совместимого с указанным в catch. При этом:

- исключение типа Т будет перехватываться обработчиками типов Т, const Т, Т& или const Т&;

- обработчики типа общедоступного базового класса перехватывают исключения типа производных классов;

- обработчики типа void* перехватывают все исключения типа указателя. Блок catch, для которого в качестве типа указано «...», обрабатывает

исключения всех типов.

Например:

try {} // выполняемый фрагмент программы

catch (EConvert& А){} /* перехватывает исключения указанного

типа EConvert */

catch (char* Mes){} /*перехватывает исключения типа char* */ catch(...) {} //перехватывает остальные исключения

Таким образом, обработчик может перехватывать исключения нескольких типов. При этом существенным оказывается порядок объявления обработчиков исключений.

Так, обработчик типа void* не должен указываться перед обработчиком типа char*, потому что при таком порядке все исключения типа char* будут обрабатываться обработчиком void*. То же самое касается недопустимости указания обработчика исключений базового класса перед обработчиками производных классов.

Например:

class Е{};

class EA.public Е{}; ... try {...}

catch (Е& е) {...} // этот обработчик перехватит все исключения catch (ЕА& е){...} // этот обработчик никогда не будет вызван

Иногда оказывается, что перехваченное исключение не может быть обработано в данном месте программы. В этом случае в обработчик включается

оператор throw без параметра, в результате исключение генерируется повторно с тем же объектом, что и первый раз. При обработке этого исключения возобновляется просмотр стека вызовов в целях обнаружения другого обработчика данного или совместимого типов.

Например:

class Е{}; II класс исключения

void somefuncQ

{1/(кусловие> throw Out(); }И функция, генерирующая исключение voidfuncQ

{try {somefunc(true); }

catch (E& e){ if () throw;} /* если здесь исключение обработать нельзя, то возобновляем его */ }

void mainfuncQ { try {funcQ; }

catch(E& e){... } } И здесь обрабатываем исключение

Стек вызовов для данного примера показан на рис. 6.1.

Функция somefunc генерирует исключение. Для его обработки осуществляется обратный просмотр стека вызовов, т.е. по очереди проверяются все функции, выполнение которых не завершено. При этом обнаруживается, что вызов sumefuncO осуществлен в защищенном блоке функции func(), и, следовательно, проверяется соответствие типа исключения типу имеющегося обработчика. Тип соответствует, следовательно исключение перехвачено, но если оно не может быть обработано в данном обработчике, то исключение генерируется вновь. Теперь в поисках обработчика исключения проверяется следующая незавершенная функция - mainfimc(). В этой функции обнаруживается, что вызов func () выполнялся в защищенном блоке. При проверке связанного с ним блока catch выясняется, что данное исключение перехватывается и обрабатывается.

Использование имени переменной в качестве параметра оператора catch позволяет операторам обработки получить доступ к аргументам исключения через указанное имя. Например:

class Е //класс исключения { public: int пит; // номер исключения

E(intn): пит(п){} //конструктор

}

throw Е(5); II генерируемое исключение

catch (Е& e){if (е.пит==5) {■■■}} И получен доступ к полю

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

1) при генерации исключения происходит конструирование временного объекта исключения;

2) выполняется поиск обработчика исключения;

3) при нахождении обработчика создается копия объекта с указанным именем;

4) уничтожается временный объект исключения;

5) выполняется обработка исключения;

6) уничтожается копия исключения с указанным именем.

Поскольку обработчику передается копия объекта исключения,

желательно в классе исключения со сложной структурой предусмотреть копирующий конструктор и деструктор.

Иногда бывает удобно указать при объявлении функции, какие исключения она может генерировать. Именно для этих исключений программист будет предусматривать обработчики при вызове функции. Указание генерируемых исключений осуществляется в спецификации исключений:

throw(,...).

Например:

voidfunc () throw (char *,int){...} /*данная функция может генерировать

исключения типов char* и int */

При организации перекрытия виртуальных методов следует учитывать, что спецификация исключений не считается частью типа функции и, следовательно, ее можно изменить:

class ALPHA { public:

struct ALPHA_ERR {}; virtual void vfuncQ throw (ALPHA_ERR) {}

II спецификация исключения

class BETA : public ALPHA { public:

void vfunc() throw(char *) {} 11 изменение спецификации

};

Если в процессе выполнения программы будет сгенерировано исключение не предусмотренного спецификацией типа, то управление будет передано специальному обработчику непредусмотренных исключений. Для определения этой функции в программе используется функция set_unexpected:

void my_unexpected() { }

set_unexpected(my unexpected);

Функция set_unexpected() возвращает старый адрес функции - обработчика непредусмотренных исключений, что позволяет организовать достаточно сложную обработку.

Если нужный обработчик при обратном просмотре стека вызовов не найден, а обработчик непредусмотренных исключений отсутствует, то вызывается функция terminate(). По умолчанию эта функция вызывает функцию abort(), которая аварийно завершает текущий процесс.

Можно установить собственную функцию завершения, используя функцию set_terminate():

void ту Jerminatе0 ^обработка завершения>}

set_terminate(myterminate);

Функция set_terminate() также возвращает адрес предыдущей программы обработки завершения.

В качестве примера использования исключений C++ можно вернуться к тексту программы примера 6.2.

Наиболее интересным является код метода ввода элементов массива из таблицы:

void TMasByte: :InputMas(TStringGrid* Gr id,int i.intj)

{ int k=0;

while (Grid->Cells[k+iJ[j].LengthQ)

{try { unsigned char x=StrToInt(Grid->Cells[k+i][i]); if (xInputMas(DataStringGrid,0,0);}

catch (char* Mes)

{ TMsgDlgButtons Set2;

Set2«mbOK;

MessageDlg(Mes, mtInformation,Set2,0); }

Помимо обычной обработки исключения C++ Builder позволяет осуществлять их завершающую обработку. Операторы завершающей обработки выполняются независимо от возникновения или отсутствия исключений в защищенном коде.

Для организации завершающей обработки используется служебное слово finally:

try {}

_ finally ^завершающая обработка>}

Механизм завершающей обработки описан далее, так как первоначально он появился в структурном управлении исключениями С.

Механизм исключений С. В языке С используется так называемое структурное управление исключениями.

В основе структурного управления исключениями лежат конструкции

_ try.._except и _ try... finally. Первая обеспечивает обычную обработку

исключения, вторая - завершающую.

Обычная обработка программируется следующим образом:

_ try {}

__ ехсер1()

{ExceptionRecord;

CONTEXT *xc = xp->Context; switch (xr->ExceptionCode)

{ case EXCEPTION_BREAKPOINT:

++xc->Eip; I* в коде программы остались встроенные точки останова перешагнем через них, изменив адрес команды на 1байт */ rc = EXCEPTION_CONTINUE_EXECUTION; break;

case EXCEPTION_ACCESS_VIOLATION: rc = EXCEPTION_EXECUTE_HANDLER; break;

default: II продолжить поиск обработчика

rc = EXCEPTIONjCONTINUEJEARCH; break; }; return rc;

}

EXCEPTION^POINTERS *xp; try { funcQ; }

__ except(xfilter(xp = GetExceptionlnformationQ)) { abort(); }

Для генерации исключения используется специальная функция

void RaiseException(DWORD ,

const DWORD * }

__ finally {Завершающая обработка>}

Или, соответственно, в C++:

try {Защищенный блок> }

__ finally {Завершающая обработка>}

Например:

try

{ float/ = 1.0, g = 0.0;

try { e = f/g;} И генерируется исключение «деление на нуль»

_ _except(EXCEPTION_EXECUTE_HANDLER)

{ }

}

_ _Jinally { Завершающая обработка> }

При выполнении завершающей обработки, так же как и при обычной обработке, вызываются деструкторы созданных локальных объектов.

То же самое происходит, если исключения остаются необработанными. Локальные объекты не уничтожаются только в том случае, если необработанным остается исключение Win32.

Совместное использование различных механизмов обработки исключений. Механизм структурного управления исключениями был создан при разработке операционной системы Windows NT, но включение библиотеки excpt.h в C++ Builder позволяет использовать этот механизм при работе с исключениями Win32 с соблюдением некоторых правил:

1) исключения Win32 можно обрабатывать только try...______ except (C++)

или _ _try... except (С) или соответственно try..._ finally (C++) или _ try...

__ finally (С); оператор catch эти исключения игнорирует;

2) неперехваченные исключения Win32 не обрабатываются функцией обработки неперехваченных исключений и функцией terminate(), а передаются операционной системе, что обычно приводит к аварийному завершению приложения;

3) обработчики исключений не получают копии объекта исключения, так как он не создается, а для получения информации об исключении используют функции GetExceptionCode () и GetExceptionlnformation ().

При одновременной обработке исключений различных типов необходимо иметь в виду, что:

- исключения C++ не видимы для_______ except (блока обработки струк

турных исключений С), а исключения С не перехватываются catch;

- каждому блоку try может соответствовать один блок________ except или

последовательность блоков catch, и попытки нарушить это правило приводят к синтаксическим ошибкам.

Если возникает необходимость перехвата структурных исключений и исключений C++ для одной последовательности операторов, то соответствующие конструкции вкладываются одна в другую.

Пример 6.5. Совместная обработка исключений различных типов.

Рассмотрим организацию совместной обработки исключения Win32 «Деление на ноль в вещественной арифметике» и исключения C++.

В файле Exception.h определим класс исключения и функцию, использующую это исключение:

Ш/ndef ExceplionH Mefine ExceplionH

class MyException // класс исключения

{private: char * what; // поле сообщения public: MyException(char* s);

MyException(const MyException& e );

~MyException(); char* msg()const;

h

float func(float /float g); tiendif

Тела методов и функции опишем в файле Exceptionxpp:

include

#pragma hdrstop

^include "Exception.h"

My Exception:: My Exception (char * s = "Unknown") { what = strdup(s); } MyException::MyException(const MyException& e){what = strdup(e.what);} MyException: :~MyException() {delete[] what; }

char* MyException::msgO const{return what;} float func (float /float g)

{float r=0; try {try

{try {r =//g; }

___ except(EXCEPTION_EXECUTE_HANDLER)

{throw (MyException(" Ошибка Деление на нуль")); }

}

catch (const MyException& e)

{ ShowMessage(AnsiString(e.msgO));}

}

_ _Jinally {ShowMessage("Завершающая обработка");} return r;

}

ftpragmapackage (smart Jnit)

Вызов функции можно осуществить следующим образом: RezEdit->Text=FloatToStr(func(StrToFloat(DivEdit->Text),

StrToFloat(DvEdit->Text)));

Примечание. При отладке программ, использующих обработку исключений, следует иметь в виду, что среда C++ Builder, так же как и среда Delphi, фиксирует любые исключения, в том числе и обрабатываемые в программе. Получив сообщение среды, необходимо продолжить выполнение программы.

6.2.

<< | >>
Источник: Г.С.Иванова, Т.Н.Ничушкина, Е.К.Пугачев. Объектно- ориентированное программирование. 2001

Еще по теме Исключения:

  1. Исключения
  2. какиe виды тайны влекут исключения из числа свидетелей.
  3. Статья 128. Исключение из состава участников полного общества
  4. ИСКЛЮЧЕНИЯ ПРИ АНАЛИЗЕ ГОЛОСОВОГО КОДА
  5. ИСКЛЮЧЕНИЯ ПРИ АНАЛИЗЕ ГОЛОСОВОГО КОДА
  6. Статья 130. Расчеты в случае выхода, исключение и убытие с полного общества
  7. § 2. Требования об освобождении имущества от ареста и исключении имущества из описи (п. 2509-2515)
  8. коян: Восходящий узел - включение в общий поток; Нисходящий узел - исключение из общего потока.
  9. § 13 Личные отношения супругов по русскому закону. – Приобщение жены к состоянию мужа. – Совместное жительство супругов. – Право мужа требовать к себе жену. – Исключение для жены осужденного, сосланного и высланного. – Право мужа следовать за женой. – Нравственная и попечительная обязанность мужа. – Право жены на содержание. – Обязанность жены повиноваться мужу.
  10. Статья 125. Изменения в составе участников полного общества
  11. Статья 447. Правовые последствия окончания срока действия имущественных прав интеллектуальной собственности на произведение