В сложных программах в качестве исключений удобно использовать не встроенные типы данных, а специально разработанные классы. C++ поддерживает базовый класс исключений, разработанный для объявления объектов, которые могут быть выброшены в виде исключений. Этот класс объявлен в пространстве имен std и называется exception. Он имеет конструктор по умолчанию, копирующий конструктор, перегруженный оператор присваивания, деструктор и виртуальный метод what, который возвращает строку (const char*), содержащую описание выброшенного исключения. Перегрузка операторов, виды конструкторов, наследование и виртуальные методы относятся к объектно-ориентированному программированию, которое не рассматривается подробно в данном пособии.
Класс exception используется в качестве базового для порождения классов исключений, предназначенных для конкретных ошибочных ситуаций. При разработке собственных классов исключений рекомендуется создавать их в виде наследников exception.
В следующем примере создается 2 собственных класса ошибок, порожденных от exception. Они практически ничем не отличаются и наследуют все члены от родительского класса. Однако с их помощью можно разделить все исключения в программе на две группы: математические и файловые.
#include <iostream>
using namespace std;
// Класс математических ошибок, порожденный от std::exception
class math_error: public exception
{
public:
// Конструктор - создает объект класса exception с описанием ошибки
math_error(const char* desc)
: exception(desc) {};
};
// Класс файловых ошибок, порожденный от std::exception
class file_error: public exception
{
public:
file_error(const char* desc)
: exception(desc) {};
};
void main()
{
setlocale(LC_ALL, "Russian");
char filename[255];
int num;
FILE* fd;
cout << "Введите название файла \n";
cin >> filename;
try
{
// Пытаемся открыть файл
fd = fopen(filename, "w");
if (fd == NULL)
// Открыть не получилось - генерируем файловую ошибку
throw file_error("Ошибка открытия файла");
cout << "Введите число, отличное от 0\n";
cin >> num;
if (!num)
// Введено неверное число - возбуждаем математическую ошибку
Конструкторы классов math_error и file_error при вызове реально создают объект класса exсeption с описанием ошибки. Ошибки различных классов перехватываются отдельными блоками catch, поэтому существует возможность обрабатывать исключения различных типов по-разному. Для вывода сообщения об ошибке используется метод what, унаследованный от класса exception. Он содержит описание ошибки, указанное в конструкторе при создании объекта.
Кроме того, в примере перехватываются стандартные исключения класса exception. Для них выводятся название класса, который был перехвачен, и описание исключения. Пример работы данного кода при различных входных значениях:
Введено неверное имя файла (не может быть создан или открыт для записи)
Введите название файла
*)(*123
Ошибка при работе с файлом:
Ошибка открытия файла
Введено недопустимое число 0
Введите название файла
d:\test.txt
Введите число, отличное от 0
Математическая ошибка:
Деление на 0
Все введено правильно, после закрытия файла выбрасывается неизвестная ошибка
Введите название файла
d:\test.txt
Введите число, отличное от 0
class std::exception
Неизвестная ошибка
При использовании классов в качестве исключений нужно учитывать тот факт, что блок catch родительского класса способен перехватывать исключения, выброшенные с использованием его потомков. Если в вышеописанном примере убрать обработчик исключения для класса math_error, то аварийной ситуации c выбросом необработанного исключения не возникнет. Так как math_error является потомком exception, то блок обработки исключений типа exception может перехватывать исключения math_error. Если при этом ввести число 0, то результатом работы программы будет следующий вывод:
Введите число, отличное от 0
class math_error
Деление на 0
Из него видно, что в отстутствии обработчика для math_error исключение такого класса перехватил обработчик его предка – exception. Он вывел название перехваченного класса (class math_error) и описание ошибки.
Из этой особенности классов следует вывод, что обработчики исключений-потомков должны в программе располагаться раньше обработчиков исключений-предков, иначе потомки никогда не смогут обработать свои исключения, т.к. они будут перехватываться catch-блоками родительских классов.