LR_5_КПиЯП_исключения

Л А Б О Р А Т О Р Н А Я Р А Б О Т А №6

Генерация и обработка исключений

Цель работы: Понять, как обрабатываются исключения. Использовать try, throw и catch для отслеживания, индикации и обработки исключений.

Краткие теоретические сведения

Исключения в С++
Основы обработки исключительных ситуаций
Механизм обработки исключительных ситуаций позволяет использовать для представления информации об ошибке объект любого типа. Поэтому можно, например, создать иерархию классов, которая будет предназначена для обработки аварийных событий. Это упростит, структурирует и сделает более понятной программу.
Рассмотрим пример обработки исключительных ситуаций. Функция div(), возвращающает частное от деления чисел, принимаемых в качестве аргументов. Если делитель равен нулю, то генерируется исключительная ситуация.
#include
double div(double dividend, double divisor)
{ if(divisor==0) throw 1;
return dividend/divisor;
}
void main()
{ double result;
try {
result=div(77.,0.);
cout<<"Answer is "< }
catch(int){
cout<<"Division by zero"< }
}
Результат выполнения программы:
Division by zero
В данном примере необходимо выделить три ключевых элемента. Во-первых, вызов функции div() заключен внутрь блока, который начинается с ключевого слова try. Этот блок указывает, что внутри него могут происходить исключительные ситуации. По этой причине код, заключенный внутри блока try, иногда называют охранным.
Далее за блоком try следует блок catch, называемый обычно обработчиком исключительной ситуации. Если возникает исключительная ситуация, выполнение программы переходит к этому catch-блоку. Хотя в этом примере имеется один-единственный обработчик, их в программах может присутствовать множество и они способны обрабатывать множество различных типов исключительных ситуаций.
Еще одним элементом процесса обработки исключительных ситуаций является оператор throw (в данном случае он находится внутри функции div()). Оператор throw сигнализирует об исключительном событии и генерирует объект исключительной ситуации, который перехватывается обработчиком catch. Этот процесс называется вызовом исключительной ситуации. В рассматриваемом примере исключительная ситуация имеет форму обычного целого числа, однако программы могут генерировать практически любой тип исключительной ситуации.
Если в инструкции if(divisor==0) throw 1 значение 1 заменить на 1. , то при выполнении будет выдана ошибка об отсутствии соответствующего обработчика catch (так как возбуждается исключительная ситуация типа double).
Одним из главных достоинств использования механизма обработки исключительных ситуаций является обеспечение развертывания стека. Развертывание стека – это процесс вызова деструкторов локальных объектов, когда исключительные ситуации выводят их из области видимости.
Сказанное рассмотрим на примере функции add() класса add_class, выполняющей сложение компонентов-данных объектов add_class и возвращающей суммарный объект. В случае если сумма превышает максимальное значение для типа unsigned short, генерируется исключительная ситуация.
#include
#include
class add_class
{ private:
unsigned short num;
public:
add_class(unsigned short a)
{ num=a;
cout<<"Constructor "< }
~add_
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·0);
try{
s=add(a,b);
cout<<"Result";
s.show_num();
cout< }
catch(int){
cout<<"Overflow error"< }
}
Результат выполнения программы:
Constructor 65535
Constructor 1
Constructor 0
Constructor 0
Destructor of add_class 0
Destructor of add_class 65535
Destructor of add_class 1
Overflow error
Destructor of add_class 0
Destructor of add_class 1
Destructor of add_class 65535
Сначала вызываются конструкторы объектов a, b и s, далее происходит передача параметров по значению в функцию (в этом случае происходит вызов конструктора копий, созданного по умолчанию, именно поэтому вызовов деструктора больше, чем конструктора), затем, используя конструктор, создается объект sum. После этого генерируется исключение и срабатывает механизм развертывания стека, то есть вызываются деструкторы локальных объектов sum, a и b. И, наконец, вызываются деструкторы s, b и a.
Рассмотрим более подробно элементы try, catch и throw механизма обработки исключений.
Блок try. Синтаксис блока:
try{
охранный код
}
список обработчиков
Необходимо помнить, что после ключевого слова try всегда должен следовать составной оператор, т.е. после try всегда следует {}. Блоки try не имеют однострочной формы, как, например, операторы if, while, for.
Еще один важный момент заключается в том, что после блока try должен следовать, по крайней мере, хотя бы один обработчик. Недопустимо нахождение между блоками try и catch какого-либо кода. Например:
int i;
try{
throw исключение;
}
i=0; // 'try' block starting on line ' номер ' has no catch handlers
catch(тип аргумент){
блок обработки исключения
}
В блоке try можно размещать любой код, вызовы локальных функций, функции-компоненты объектов, и любой код любой степени вложенности может генерировать исключительные ситуации. Блоки try сами могут быть вложенными.
Обработчики исключительных ситуаций catch. Обработчики исключительных ситуаций являются важнейшей частью всего механизма обработки исключений, так как именно они определяют поведение программы после генерации и перехвата исключительной ситуации. Синтаксис блока catch имеет следующий вид:
catch(тип 1 <аргумент>)
{
тело обработчика
}
catch(тип 2 <аргумент>))
{
тело обработчика
}
.
.
.
catch(тип N <аргумент>))
{
тело обработчика
}
Таким образом, так же как и в случае блока try, после ключевого слова catch должен следовать составной оператор, заключенный в фигурные скобки. В аргументах обработчика можно указать только тип исключительной ситуации, не обязательно объявлять имя объекта, если этого не требуется.
У каждого блока try может быть множество обработчиков, каждый из которых должен иметь свой уникальный тип исключительной ситуации. Неправильной будет следующая запись:
typedef int type_int;
try{
. . .
}
catch(type_int error1){
. . .
}
catch(int error2){
. . .
}
Так, в этом случае type_int и int - это одно и то же.
Однако следующий пример верен.
class cls
{ public:
int i;
};

try{
. . .
}
catch(cls i1){
...
}
catch(int i2){
}
В этом случае cls – это отдельный тип исключительной ситуации. Существует также абсолютный обработчик, который совместим с любым типом исключительной ситуации. Для написания такого обработчика надо вместо аргументов написать многоточие (эллипсис).
catch (){
блок обработки исключения
}
Использование абсолютного обработчика исключительных ситуаций рассмотрим на примере программы, в которой происходит генерация исключительной ситуации типа char *, но обработчик такого типа отсутствует. В этом случае управление передается абсолютному обработчику.
#include
#include
void int_exception(int i)
{ if(i>100) throw 1;
}
void string_exception()
{ throw "Error";
}
void main()
{ try{
int_exception(99);
string_exception();
}
catch(int){
cout<<"Обработчик для типа Int";
getch();
}
catch(...){
cout<<"Абсолютный обработчик ";
getch();
}
Результат выполнения программы:
Абсолютный обработчик
Так как абсолютный обработчик перехватывает исключительные ситуации всех типов, то он должен стоять в списке обработчиков последним. Нарушение этого правила вызовет ошибку при компиляции программы.
Для того чтобы эффективно использовать механизм обработки исключительных ситуаций, необходимо грамотно построить списки обработчиков, а для этого, в свою очередь, нужно четко знать следующие правила, по которым осуществляется поиск соответствующего обработчика:
- исключительная ситуация обрабатывается первым найденным обработчиком, т. е. если есть несколько обработчиков, способных обработать данный тип исключительной ситуации, то она будет обработана первым стоящим в списке обработчиком;
- абсолютный обработчик может обработать любую исключительную ситуацию;
- исключительная ситуация может быть обработана обработчиком соответствующего типа либо обработчиком ссылки на этот тип;
- исключительная ситуация может быть обработана обработчиком базового для нее класса. Например, если класс В является производным от класса А, то обработчик класса А может обработать исключительную ситуацию класса В;
- исключительная ситуация может быть обработана обработчиком, принимающим указатель, если тип исключительной ситуации может быть приведен к типу обработчика, путем использования стандартных правил преобразования типов указателей.
Если при возникновении исключительной ситуации подходящего обработчика нет среди обработчиков данного уровня вложенности блоков try, то обработчик ищется на следующем охватывающем уровне. Если обработчик не найден вплоть до самого верхнего уровня, то программа аварийно завершается.
Следствием из правил 3 и 4 является еще одно утверждение: исключительная ситуация может быть направлена обработчику, который может принимать ссылку на объект базового для данной исключительной ситуации класса. Это значит, что если, например, класс В – производный от класса А, то обработчик ссылки на объект класса А может обрабатывать исключительную ситуацию класса В (или ссылку на объект класса В).
Рассмотрим особенности выбора соответствующего обработчика на следующем примере. Пусть имеется класс С, являющийся производным от классов А и В; показано, какими обработчиками может быть перехвачена исключительная ситуация типа С и типа указателя на С.
#include
class A{};
class B{};
class C : public A, public B {};

void f(int i)
{ if(i) throw C(); // возбуждение исключительной ситуации
// типа объект С
else throw new C; // возбуждение исключительной ситуации
// типа указатель на объект класса С
}

void 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* может быть приведен любой указатель, то обработчик этого типа будет перехватывать любые исключительные ситуации типа указателя.
Генерация исключительных ситуаций throw. Исключительные ситуации передаются обработчикам с помощью ключевого слова throw. Как ранее отмечалось, обеспечивается вызов деструкторов локальных объектов при выходе из области видимости, то есть развертывание стека. Однако развертывание стека не обеспечивает уничтожение объектов, созданных динамически. Таким образом, перед генерацией исключительной ситуации необходимо явно освободить динамически выделенные блоки памяти.
Следует отметить также, что если исключительная ситуация генерируется по значению или по ссылке, то создается скрытая временная переменная, в которой хранится копия генерируемого объекта. Когда после throw указывается локальный объект, то к моменту вызова соответствующего обработчика этот объект будет уже вне области видимости и, естественно, прекратит существование. Обработчик же получит в качестве аргумента именно эту скрытую копию. Из этого следует, что если генерируется исключительная ситуация сложного класса, то возникает необходимость снабжения этого класса конструктором копий, который бы обеспечил корректное создание копии объекта.
Если же исключительная ситуация генерируется с использованием указателя, то копия объекта не создается. В этом случае могут возникнуть проблемы. Например, если генерируется указатель на локальный объект, к моменту вызова обработчика объект уже перестанет существовать и использование указателя в обработчике приведет к ошибкам.
Перенаправление исключительных ситуаций
Иногда возникает положение, при котором необходимо обработать исключительную ситуацию сначала на более низком уровне вложенности блока try, а затем передать ее на более высокий уровень для продолжения обработки. Для того чтобы сделать это, нужно использовать throw без аргументов. В этом случае исключительная ситуация будет перенаправлена к следующему подходящему обработчику (подходящий обработчик не ищется ниже в текущем списке - сразу осуществляется поиск на более высоком уровне). Приводимый ниже пример демонстрирует организацию такой передачи. Программа содержит вложенный блок try и соответствующий блок catch. Сначала происходит первичная обработка, затем исключительная ситуация перенаправляется на более высокий уровень для дальнейшей обработки.
#include
void func(int i)
{ try{
if(i) throw "Error";
}
catch(char *s) {
cout< throw;
}
}
void main()
{ try{
func(1);
}
catch(char *s) {
cout< }
}
Результат выполнения программы:
Error - выполняется первый обработчик
Error - выполняется второй обработчик
Если ключевое слово trow используется вне блока catch, то автоматически будет вызвана функция terminate(), которая по умолчанию завершает программу.

Порядок выполнения работы
Изучить краткие теоретические сведения.
Написать, отладить и выполнить программу.


Варианты заданий.

1. Реализовать базовый класс исключений и многоуровневую иерархию производных классов с использованием наследования, таких как выход индекса за пределы массива, неправильный аргумент у функции, переполнение сверху, переполнение снизу, ошибка диапазона, ошибка выделения памяти и др. На основе класса String продемонстрировать генерацию исключений. Промоделировать ситуацию, в которой будет важна последовательность вызова обработчиков в иерархии исключений.

2. Определите класс Int, который ведет себя точно также как и встроенный тип int, за исключением того, что он генерирует исключения, не допуская переполнения сверху и снизу.

3. Напишите программу, которая контролирует индексы, выходящие за пределы массива, и генерирует исключения. В сообщение об ошибке должна входить информация о значении индекса, приведшего к сбою.

4. Перегрузите операцию + для объединения двух строк. Добавьте класс исключений, генерируйте исключения в конструкторе с одним аргументом в случае, если строка инициализации слишком длинная. Генерируйте еще одно исключение в перегруженном операторе +, если результат конкатенации оказывается слишком длинным. Сообщайте пользователю о том, какая именно ошибка произошла.
15

Приложенные файлы

  • doc 4989497
    Размер файла: 64 kB Загрузок: 0

Добавить комментарий