Исключения в языке С++

Лекция 18.

Исключения.

Язык С++ имеет встроенный механизм обработки ошибок, называемый обработкой исключений (exception handling). Назначение этого механизма в том, чтобы в момент возникновения ошибки сохранить информацию о ней в объекте исключения и передпать этот объект туда, где известно, как реагировать на ошибочную ситуацию.

Создание и перехват исключений.

Механизм исключений использует три ключевых слова: throw, try, catch. Исключительная ситуация создается (говорят «выбрасывается исключение») при помощи оператора throw:

throw значение;

где значение идентифицирует возникшее исключение и может быть любого типа.

Если исключение не перехватывается в той же функции, где оно было выброшено, оператор throw завершает функцию подобно оператору return.

Код, который может выбросить исключения (рискованный код), помещают в блок try.

try

{// здесь код, способный выбросить исключения

}

а код обработки исключений помещают в блок catch, который всегда следует за блоком try.

catch (исключение)

{ // здесь код обработки исключения

}

Все исключения соответствующего типа, выбрасываемые внутри блока try, попадают в блок catch и там обрабатываются.

Пример. Исключение выбрасывается в данной функции.

try

{ if (b==0)

throw 0;

double d=a /b;

cout << d;

}

catch (int)

{ cout<<”ERROR: division by zero.”;

}

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

Пример. Исключение выбрасывается в вызываемой функции.

double Son (double a, double b)

{ if (b)

return a / b;

else

throw 0;

}

void Father()

{try

{ double d=Son(3, 0);

cout<< d;

}

}

catch (int)

{ cout<<”ERROR: division by zero.”;

}

}

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

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

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

void main ()

{ double x=8.0, y;

try

{ if (x==0)

throw 1;

if (x < 0)

throw “abc”;

y = sqrt(1 / x);

}

catch ( int )

{ cout << “Деление на 0”;

}

catch (char *)

{ cout << “Отрицательное число ”;

}

}

Однотипные исключения можно различать по их значению.

Пример. Обработка однотипных, но разных исключений.

try

{ if (x==0)

throw 1;

if (x < 0)

throw 2;

y = sqrt(1 / x);

}

catch ( int ex)

{

switch (ex)

{

case 1:

cout << “Деление на 0”;

break;

case 2:

cout << “ Отрицательное число”;

break;

}

}

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

Чтобы перехватить в блоке catch абсолютно все исключения, его записывают с аргументом многоточие

catch ( . . .)

{ // здесь код обработки исключений.

}

Такой блок следует располагать последним в серии блоков catch ( ).

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

Замечание. Если в параметре передается объект предка, а не указатель на него, при обработке исключения нельзя будет воспользоваться динамическим приведением типа и полиморфизмом.

Стандартная обработка исключений.

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

она вызывает функцию abort() из стандартной библиотеки, но может быть изменена функцией set_terminate().

Повторная обработка исключения.

Обработанное исключение автоматически уничтожается. Если необходимо продолжить обработку исключения во внешнем блоке try, его следует повторно выбросить оператором throw без параметра. Смысл повторного создания исключения в том, чтобы разослать информацию об ошибке нескольким обработчикам.

Пример. Повторное вобуждение исключения.

try

{

try

{ if (!x ) throw 1;

y=1 / x;

}

catch (int mes) //внутренний блок

{ if (mes == 1)

cout << “ Деление на 0 ”;

throw ;

}

}

catch (int) // внешний блок

{

cout << “Повторяю, деление на 0”;

}

Спецификация исключений в функции.

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

Пример спецификации исключений.

double Son (double a, double b) throw(int, bool);

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

void f() throw();

Стандартные исключения.

В заголовочном файле стандартной библиотеки <stdexcept> объявлены классы исключений, которыми следует пользоваться. Отступами показана иерархия классов.

class exception;

class logic_error;

class domain_error;

class invalid_argument;

class length_error;

class out_of_range;

class runtime_error;

class range_error;

class overflow_error;

class underflow_error;

А теперь работающий пример.

// Демонстрация механизма исключений

#include <iostream>

using namespace std;

const int MAX = 3; //в стеке максимум 3 целых числа

///////////////////////////////////////////////////////////

class Stack

{

private:

int st[MAX]; //стек: целочисленный массив

int top; //индекс вершины стека

public:

class Range //класс исключений для Stack

{ //внимание: тело класса пусто

};

//---------------------------------------------------------

Stack() //конструктор

{ top = -1; }

//---------------------------------------------------------

void push(int var)

{

if(top >= MAX-1) //если стек заполнен,

throw Range(); //генерировать исключение

st[++top] = var; //внести число в стек

}

//---------------------------------------------------------

int pop()

{

if(top < 0) //если стек пуст,

throw Range(); //исключение

return st[top--]; //взять число из стека

}

};

///////////////////////////////////////////////////////////

int main()

{

Stack s1;

try

{

s1.push(11);

s1.push(22);

s1.push(33);

// s1.push(44); //Оп! Стек заполнен

cout << "1: " << s1.pop() << endl;

cout << "2: " << s1.pop() << endl;

cout << "3: " << s1.pop() << endl;

cout << "4: " << s1.pop() << endl;

//Оп! Стек пуст

}

catch(Stack::Range) //обработчик

{

cout << "Исключение: Стек переполнен или пуст"<<endl;

}

cout << "Приехали сюда после захвата исключения (или нормального выхода" << endl;

return 0;

}

Последовательность событий в программе –

1)Код нормально выполняется вне блока повторных попыток

2)Управление переходит в блок повторных попыток

3)Какое-то выражение приводит к появлению ошибки в методе

4)Метод генерирует исключение

5)Управление переходит к обработчику ошибок

Пример с генерацией нескольких исключений

// xstak2.cpp

// Демонстрация обработчика двух исключений

#include <iostream>

using namespace std;

const int MAX = 3; //в стеке может быть до трех целых чисел

class Stack

{

private:

int st[MAX]; //стек: массив целых чисел

int top; //индекс верхушки стека

public:

class Full { }; //класс исключения

class Empty { }; //класс исключения

//---------------------------------------------------------

Stack() //конструктор

{ top = -1; }

//---------------------------------------------------------

void push(int var) //занести число в стек

{

if(top >= MAX-1) //если стек полон,

throw Full(); //генерировать исключение Full

st[++top] = var;

}

//---------------------------------------------------------

int pop() //взять число из стека

{

if(top < 0) //если стек пуст,

throw Empty(); //генерировать исключение Empty

return st[top--];

}

};

int main()

{

Stack s1;

try

{

s1.push(11);

s1.push(22);

s1.push(33);

// s1.push(44); //Оп!: стек уже полон

cout << "1: " << s1.pop() << endl;

cout << "2: " << s1.pop() << endl;

cout << "3: " << s1.pop() << endl;

cout << "4: " << s1.pop() << endl;

//Оп!: стек пуст

}

catch(Stack::Full)

{

cout << "Ошибка: переполнение стека" << endl;

}

catch(Stack::Empty)

{

cout << "Ошибка: стек пуст" << endl;

}

return 0;

}

Исключения с аргументами

#include <iostream>

#include <string>

using namespace std;

class Distance //класс английских расстояний

{

private:

int feet;

float inches;

public:

//---------------------------------------------------------

class InchesEx //класс исключений

{

public:

string origin; //для имени функции

float iValue; //для хранения ошибочного

//значения

InchesEx(string or, float in) //конструктор с

//двумя аргументами

{

origin = or; //сохраненная строка

//с именем виновника ошибки

iValue = in; //сохраненное неправильно

//значение дюймов

}

}; //конец класса исключений

//---------------------------------------------------------

Distance() //конструктор (без аргументов)

{ feet = 0; inches = 0.0; }

//---------------------------------------------------------

Distance(int ft, float in) //конструктор (2 аргумента)

{

if(in >= 12.0)

throw InchesEx("Конструктор с двумя аргументами", in);

feet = ft;

inches = in;

}

//---------------------------------------------------------

void getdist() //получить данные от пользователя

{

cout << "\nВведите футы: "; cin >> feet;

cout << "Введите дюймы: "; cin >> inches;

if(inches >= 12.0)

throw InchesEx("функция getdist()", inches);

}

//---------------------------------------------------------

void showdist() //вывести расстояние

{ cout << feet << "\'-" << inches << '\"'; }

};

int main()

{

try

{

Distance dist1(17, 3.5); //конструктор с двумя

//аргументами

Distance dist2; //конструктор без аргументов

dist2.getdist(); //получить значение

//вывести расстояния

cout << "\ndist1 = "; dist1.showdist();

cout << "\ndist2 = "; dist2.showdist();

}

catch(Distance::InchesEx ix) //обработчик ошибок

{

cout << "\nОшибка инициализации. Виновник: " << ix.origin

<< ".\n Введенное значение дюймов " << ix.iValue

<< " слишком большое.";

}

cout << endl;

return 0;

}

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

throw(<тип1>, <тип2>, …)

Если выбрасывается исключение, не предусмотренное в спецификации, то рассматривается unexpected exception, который также может быть обработан. Чтобы запретить выбрасывать исключения, используют спецификацию исключений без типа. Функции без спецификаций могут выбрасывать исключения любого типа.

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

int main()

{int ParamValue=0;

//Здесь ввести целочисленный параметр от 1 до 4

//и записать его в ParamValue.

. . . . . . . . . . . . . . . .

// Собственно работа

try

{

f1(ParamValue);

}

catch (int ParaExeption)

{

// Распечатка ParaExeption

}

catch (. . . )

{

// Распечатка чего-нибудь

}

void f1(int ParamValue)

{

try

{

f2(Param Value);

}

catch (long longParamEX}

{

// Распечатка longParamEX

}

void f2 (int param Value)

{

try

{

f3(param Value);

}

catch (double doubleParamEx)

{

// распечатать doubleParamEx;

}

}

void f3 (int paramValue)

{

try

{

switch (param Value)

{case 1: throw (int)1; //->main

case 2: throw (long) 2; //->f1

case 3: throw (double) 3; //->f2

case 4: throw (“4”); //здесь

}

}

catch (const char * paramEx)

{

// распечатать paramEx;

}

. . . . . . . .

}

main()

{ –>f1(x)–>f2(x)–f3(x);

}

Исключения в языке С++