Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТП лекции Раздел 4.doc
Скачиваний:
16
Добавлен:
28.09.2019
Размер:
2.56 Mб
Скачать

4.11.4. Обработка исключений.

Имеется ввиду обработка анормальных (оши­бочных) ситуаций, возникающих на этапе исполнения программы. Сам процесс обработки обозначается термином Exception Handling. Выявление и обработка аварийных ситуаций производится с помощью специальных конструкций, использующих ключевые слова try, catch и throw.

Если не разрабатывать своих собственных обработчиков ситуаций (handlers), то компилятор по умолчанию создает стандартную процедуру завершения программы (termination). Однако обозначение конкретной аварийной ситуации (throwing an exception) позволяет предпринять какие-то определенные действия по ее обра­ботке и, возможно, продолжению работы программы. При этом рассматривают­ся только ситуации внутреннего (synchronous) характера (нет памяти в области heap, не найден файл, переполнение и т. д.). Ситуация, созданная нажатием кла­виш Ctrl+C, считается внешней и не может быть обработана.

Блок кодов вида try {...} выделяет ту часть программы, в которой ожидаются аварийные ситуа­ции, и на них предусмотрен и определен перечень нестандартных действий. Непосредственно за ним должен следовать хотя бы один блок вида catch () {...}, который и является обработчиком ситуации. Например:

void main()

{

long double **array;

long n=l;

cout « "\n\nAllocation Exception Demonstration\n\n";

while (n)

{

cout « "Enter matrix dimension (0 - to exit): "; cin » n;

try // Блок, в котором возможна авария

{

array = new long double* [n];

if (! array) // Так мы узнаем об отказе

throw "Allocation failure"; // Выбрасываем флаг аварии

for (int 1=0; i<n: i++)

array[i] = new long double[n];

If (!array[i]) throw "Allocation failureXn";

}

catch (char* s)

{ // Обработчик ситуации

cout « s « n «" elements\n\n";

}

}

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

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

• throw <expression> — эта форма в случае аварии передает обработчику выражение, опре­деляемое программистом;

• throw; — эта форма повторно вызывает уже выбранный в качестве текуще­го обработчик;

• void func() throw() { тело функции } — в этой форме указано, что функция void func() не должна создавать аварийных ситуаций, или что они обраба­тываются внутри func. Если все же они возникнут, то управление передает­ся действующей в данный момент функции unexpected(), которая по умол­чанию вызывает функцию abort();

• void func() throw(A, В) { тело функции } — эта форма throw определяет список аварийных ситуаций, которые может выбросить функция func().

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

set_unexpected (MyHandler);

задает функцию void MyHandler() обработчиком неожиданных исключений, на­чиная с точки задания. Функция MyHandler() должна быть заранее определена и находиться в области видимости. Обработчиком ситуации является один из catch-блоков. Они должны следовать за try-блоком. Синтаксически возможны две формы catch-блока:

catch (Type х) {}

catch (...){}

Первая форма позволяет обработать объект х типа Туре, который выбрасыва­ет блок try в точке throw. Вторая позволяет обработать любую исключительную ситуацию независимо от типа. Если она используется, то должна стоять после­дней в списке обработчиков. Аварийная ситуация считается пойманной, если ее тип совпадает с типом, указанным в операторе catch(). После обработки ситуа­ции управление передается в точку программы, которая следует за последним блоком catch.

Int x;

try

{

if (x<0) throw 0;

else if (x> MAX_VALUE) throw “Out of scope”;

}

catch (int)

{…}

catch (char *error_message)

{ puts (error_message);

trow; // передаем обработку во внешний блок с контролем,

// который в примере не показан

}

catch(…) // использоваться не будет, т.к. все ситуации предусмотрены

{

}

Суффикс throw() или throw(Error) при какой-либо функции не влияет на тип функции. Так, две функции int f() throw(){} и int f(){} расцени­ваются компилятором как повторное определение одной и той же функции. Для сравнения вспомните, что суффикс const изменяет тип функции. В связи с этим надо проявлять осторожность при описании виртуальных функций. Переопре­деление виртуальной функции в производном классе должно иметь такой же суффикс, как и в базовом. Если суффикс содержит имя класса, например throw (А), то объект класса, производного от class А, считается удовлетворяющим за­данной ситуации. Например:

class A

{ // Базовый класс

protected:

int j;

publiс:

A (int k=0) { j=k; }

virtual int getj() throw(A);

}

int A :: getj ()throw(A)

{

if (j== 0) throw *this; // Аварийная ситуация

return j;

}

class В: public A :// Произв

{ public

В (int k=0) ;

A (k) {}

int getj() throw(B);

}

int В :: getj() throw(B)

{

i

//Аварийная ситуация

f (j==0)

throw *this;

return j;

}

void main()

{

A a(l), aa;

В b(2), Bb;

cout«"\n Exception Handling Demonstration\n\n";

try

{

cout « "\nj=" « a.getj (); cout « "\nj=" « aa.getj();

}

catch (A x)

{

cout«"\n Exception j=0. Class A";

}

try

{

cout « "\nj=" « b.getj (); cout « "\nj=" «bb.getj();

}

catch (A x)

{

cout«"\n Exception j=0. Class B";

}

cout « "\n\n Execution continues after exception " <<

" handling in the catch() block"«"\n\n";

}

В данном примере демонстрируется «вылавливание» ситуации: равенство нулю поля j в объектах класса А или равенство нулю того же, наследованного поля в объектах производного класса В. Обработчик ситуации catch (A x) {} в обоих случаях справляется с задачей поймать объект одного из классов иерархии. Си­туацию обозначаем мы сами в виртуальной функции getj.

Мы использовали два блока try-catch с целью иллюстрации идентичности объектов (аа,№) в смысле реакции системы на ситуацию j==0. Если все коды разместить в одном блоке try { } и оставить один блок catch(), то программа прервется после первой же ситуации j==0. Отметим, что оператор catch (A x) в нашем случае можно заменить на catch (...), и результат будет тем же.