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


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

Например, в следующей программе происходит деление чисел:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

double divide(int, int);

int main()
{
int x = 500;
int y = 0;
double z = divide(x, y);

std::cout << z << std::endl;
std::cout << "The End..." << std::endl;
return 0;
}

double divide(int a, int b)
{
return a / b;
}
Эта программа успешно скомпилируется, но при ее выполнении возникнет ошибка, поскольку в коде производится деление на ноль, после чего программа аварийно завершится.

С одной стороны, мы можем в функции divide определить проверку и выполнять деление, если параметр b не равен 0. Однако нам в любом случае надо возвращать из функции divide некоторый результат - некоторое число. То есть мы не можем просто написать:

1
2
3
4
5
6
7
double divide(int a, int b)
{
if (b != 0)
return a / b;
else
std::cout << "Error! b must not be equal to 0" << std::endl;
}
И в этом случае нам надо известить систему о возникшей ошибке. Для этого используется оператор throw.

Оператор throw генерирует исключение. Через оператор throw можно передать информацию об ошибке. Например, функция divide могла бы выглядеть следующим образом:

1
2
3
4
5
6
double divide(int a, int b)
{
if (b == 0)
throw "Division by zero!";
return a / b;
}
То есть если параметр b равен 0, то генерируем исключение.

Но это исключение еще надо обработать в коде, где будет вызываться функция divide. Для обработки исключений применяется конструкция try...catch. Она имеет следующую форму:

1
2
3
4
5
6
7
8
try
{
инструкции, которые могут вызвать исключение
}
catch(объявление_исключения)
{
обработка исключения
}
В блок после ключевого слова try помещается код, который потенциально может сгенерировать исключение.

После ключевого слова catch в скобках идет параметр, который передает информацию об исключении. Затем в блоке производится собственно обработка исключения.

Так изменим весь код следующим образом:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

double divide(int, int);

int main()
{
int x = 500;
int y = 0;
try
{
double z = divide(x, y);
std::cout << z << std::endl;
}
catch (...)
{
std::cout << "Error!" << std::endl;
}
std::cout << "The End..." << std::endl;
return 0;
}

double divide(int a, int b)
{
if (b == 0)
throw "Division by zero!";
return a / b;
}
Код, который потенциально может сгенерировать исключение - вызов функции divide помещается в блок try.

В блоке catch идет обработка исключения. Причем многоточие в скобках после оператора catch (catch(...)) позволяет обработать любое исключение.

В итоге когда выполнение программы дойдет до строки double z = divide(x, y);, будет сгенерировано исключение, поэтому последующие инструкции из блока try выполняться не будут, а управление перейдет в блок catch, в котором на консоль просто выводится сообщение об ошибке. После выполнения блока catch программа аварийно не завершится, а продолжит свою работу, выполняя операторы после бллока catch:

Error!
The End...
Чтобы скомпилировать данный пример с конструкцией try...catch с помощью g++ может потребоваться использование флага -static:

g++ app.cpp -o app -static
Однако в данном случае мы только знаем, что произошла какая-то ошибка, а какая именно, неизвестно. Поэтому в выражении catch мы можем получить то сообщение, которое передается оператору throw:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main()
{
int x = 500;
int y = 0;
try
{
double z = divide(x, y);
std::cout << z << std::endl;
}
catch (const char* msg)
{
std::cout << msg << std::endl;
}
std::cout << "The End..." << std::endl;
return 0;
}
С помощью параметра const char* msg получаем сообщение, которое предано оператору throw, и выводит это сообщение на консоль. И в этом случае консольный вывод будет выглядеть следующим образом:

Division by zero!
The End...
Таким образом, мы можем узнать суть возникшего исключения.