Блок обработки исключения
}
Использование абсолютного обработчика исключительных ситуаций рассмотрим на примере программы, в которой происходит генерация исключительной ситуации типа char *, но обработчик такого типа отсутствует. В этом случае управление передается абсолютному обработчику.
#include <iostream>
using namespace std;
void int_exception(int i)
{ if(i>100) throw 1; // генерация исключения типа int
}
void string_exception()
{ throw "Error"; // генерация исключения типа char *
}
int main()
{ try{ // в блоке возможна обработка одного из двух исключений
int_exception(99); // возможно исключение типа int
string_exception(); // возможно исключение типа char *
}
catch(int){
cout<<"Обработчик для типа int"<<endl;
}
catch(...){
cout<<"Абсолютный обработчик "<<endl;
}
}
Результат выполнения программы:
Абсолютный обработчик
Так как абсолютный обработчик перехватывает исключительные ситуации всех типов, то он должен стоять в списке обработчиков последним. Нарушение этого правила вызовет ошибку при компиляции программы.
Для того чтобы эффективно использовать механизм обработки исключительных ситуаций, необходимо грамотно построить списки обработчиков, а для этого, в свою очередь, нужно четко знать следующие правила, по которым осуществляется поиск соответствующего обработчика:
- исключительная ситуация обрабатывается первым найденным обработчиком, т. е. если есть несколько обработчиков, способных обработать данный тип исключительной ситуации, то она будет обработана первым стоящим в списке обработчиком;
- абсолютный обработчик может обработать любую исключительную ситуацию;
- исключительная ситуация может быть обработана обработчиком соответствующего типа либо обработчиком ссылки на этот тип;
- исключительная ситуация может быть обработана обработчиком базового для нее класса. Например, если класс В является производным от класса А, то обработчик класса А может обработать исключительную ситуацию класса В;
- исключительная ситуация может быть обработана обработчиком, принимающим указатель, если тип исключительной ситуации может быть приведен к типу обработчика путем использования стандартных правил преобразования типов указателей.
Если при возникновении исключительной ситуации подходящего обработчика нет среди обработчиков данного уровня вложенности блоков try, то обработчик ищется на следующем охватывающем уровне. Если обработчик не найден вплоть до самого верхнего уровня, то программа аварийно завершается.
Следствием из правил 3 и 4 является еще одно утверждение: исключительная ситуация может быть направлена обработчику, который может принимать ссылку на объект базового для данной исключительной ситуации класса. Это значит, что если, например, класс В – производный от класса А, то обработчик ссылки на объект класса А может обрабатывать исключительную ситуацию класса В (или ссылку на объект класса В).
Рассмотрим особенности выбора соответствующего обработчика на следующем примере. Пусть имеется класс С, являющийся производным от классов А и В; показано, какими обработчиками может быть перехвачена исключительная ситуация типа С и типа указателя на С.
#include<iostream.h>
using namespace std;
class A{};
class B{};
class C : public A, public B {};
void f(int i)
{ if(i) throw C(); // возбуждение исключительной ситуации
// типа «объект класса С»
else throw new C; // возбуждение исключительной ситуации
// типа «указатель на объект класса С»
}
int main()
{ int i;
try{
cin>>i;
f(i);
}
catch(A) {
cout<<"A handler";
}
catch(B&) {
cout<<"B& handler";
}
catch(C) {
cout<<"C handler";
}
catch(C*) {
cout<<"C* handler";
}
catch(A*) {
cout<<"A* handler";
}
catch(void*) {
cout<<"void* handler";
}
}
В данном примере исключительная ситуация класса С может быть направлена любому из обработчиков A, B& или C, поэтому выбирается обработчик, стоящий первым в списке. Аналогично для исключительной ситуации, имеющей тип указателя на объект класса С, выбирается первый подходящий обработчик A* или C*. Эта ситуация также может быть обработана обработчиками void*. Так как к типу void* может быть приведен любой указатель, то обработчик этого типа будет перехватывать любые исключительные ситуации типа указателя. Рассмотрим еще один пример:
#include <iostream.h>
using namespace std;
class base {};
class derived : public base{};
void fun()
{ derived obj;
base &a = obj;
throw a;
}
int main()
{ try{ fun();
}
catch(derived){ cout<<”derived”<<endl;
}
catch(base) { cout<<”base”<<endl;
}
}
Результат выполнения программы:
base
В примере генерируется исключение, имеющее тип base, хотя ссылка a имеет тип base, хотя является ссылкой на класс derived. Так происходит потому, что статический тип a равен base, а не derived.
В случае, когда генерация исключения выполняется по значению объекта или по ссылке на объект, происходит копирование значения объекта (локального) во временный (в catch).
Генерация исключительных ситуаций throw.Исключительные ситуации передаются обработчикам с помощью ключевого слова throw. Как ранее отмечалось, обеспечивается вызов деструкторов локальных объектов при выходе из области видимости, т.е. развертывание стека. Однако развертывание стека не обеспечивает уничтожения объектов, созданных динамически. Таким образом, перед генерацией исключительной ситуации необходимо явно освободить динамически выделенные блоки памяти.
Следует отметить также, что если исключительная ситуация генерируется по значению или по ссылке, то создается скрытая временная переменная, в которой хранится копия генерируемого объекта. Когда после throw указывается локальный объект, то к моменту вызова соответствующего обработчика этот объект будет уже вне области видимости и, естественно, прекратит существование. Обработчик же получит в качестве аргумента именно эту скрытую копию. Из этого следует, что если генерируется исключительная ситуация сложного класса, то возникает необходимость снабжения этого класса конструктором копий, который бы обеспечил корректное создание копии объекта.
Если же исключительная ситуация генерируется с использованием указателя, то копия объекта не создается. В этом случае могут возникнуть проблемы. Например, если генерируется указатель на локальный объект, к моменту вызова обработчика объект уже перестанет существовать и использование указателя в обработчике может привести к ошибкам.