Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Итог_Пособие C++.doc
Скачиваний:
8
Добавлен:
01.07.2025
Размер:
2 Мб
Скачать
☆

4.5 "Аппаратные" и "программные" исключения

До сих пор мы рассматривали исключения, которые можно условно назвать "программными" − они возбуждаются с помощью вызова оператора throw (неважно, написан ли он нами, или скрыт где-то внутри реализации используемых библиотечных функций). Возникает вопрос, а можно ли на языке C++ обрабатывать так называемые "аппаратные" исключения, источником которых являются события в процессоре и/или в операционной системе. Простейшим примером является деление целого числа на ноль. Будет ли корректно работать следующий код?

int x, a, b;

try { x = a / b;}

catch(…) {std::cout << "Деление на 0\n";}

Разберёмся, что происходит при делении двух целых чисел. Если делитель оказывается равным нулю, то внутри процессора происходит прерывание. Выполнение программы приостанавливается, и вызывается обработчик этого прерывания − специальная подпрограмма операционной системы. В результате операционная система узнаёт о том, что произошло, а также в каком именно процессе (работающей программе) это случилось. Вопрос заключается в том, может ли данная информация каким-то образом быть передана операционной системой нашему процессу. При этом ещё и должна выполниться конвертация этого события в определённый тип исключений языка C++.

Возможно ли это, зависит от конкретной операционной системы и компилятора. Например, в ОС Windows встроен механизм, который называется "структурная обработка исключений" (Structured Exception Handling, SEH). Поддержка таких исключений имеется в компиляторе Microsoft Visual C++ (по умолчанию она выключена, для включения нужно использовать ключ компиляции /EHa либо соответствующую настройку проекта). Более того, в MS Visual C++ даже имеются специализированные операторы try-except и try-finally для работы с SHE (поскольку данные операторы отсутствуют в стандарте языка С++, в данной книге мы их рассматривать не будем).

Заметим, что поддержка подобных исключений в настоящее время отсутствует в текущей версии компилятора GNU C++ для Windows (из пакета mingw). Однако, версия компилятора GNU C++ для ОС Linux похожий вид исключений поддерживает.

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

4.6 Стандартные классы исключений

Cтандартная библиотека языка C++ содержит следующую иерархию классов исключений. Корневой класс исключения называется std::exception, он объявлен в заголовочном файле exception. Данный класс содержит виртуальный метод what, возвращающий С-строку с описанием возникшего исключения. Классы, производные от exception, могут заместить метод what собственной версией.

От класса std::exception наследуется несколько классов: std::bad_alloc (ошибка выделения памяти), std::bad_cast (ошибка динамического преобразования типов), std::bad_typeid (применение оператора typeid к пустому указателю) и несколько других. Особо стоит рассмотреть два производных класса − std::logic_error (логическая ошибка) и std::runtime_error (ошибка времени выполнения).

Исключения типа std::logic_error предполагают нарушения внутренней логики программы − например, логических предусловий или инвариантов класса. Зачастую такие ошибки можно найти и предотвратить ещё до начала выполнения программы. В стандартной библиотеке определены несколько наследников класса std::logic_error:

• std::domain_error − ошибка домена. Пример: sqrt(-1).

• std::invalid_argument − некорректный аргумент. Пример: std::bitset<5> mybitset (std::string("01234")); Исключение возникнет из-за того, что строка- аргумент конструктора класса bitset может содержать лишь символы 0 или 1.

• std::length_error − ошибка длины. Пример: попытаемся установить размер вектора выше максимально допустимого размера: std::vector<int> v; v.resize(v.max_size()+1);

• std::out_of_range − выход за границы. Пример: std::vector<int> a(10); a.at(20) = 5;

Некоторые из этих исключений удобно использовать и в своих функциях, чтобы не создавать собственные классы исключений. Например, если функция получила аргумент с некорректным значением, можно возбудить исключение std::invalid_argument. Если значение аргумента выходит за пределы допустимого диапазона, можно возбудить исключение std::out_of_range. При попытке создать объект, длина которого превышает максимально возможную, можно возбудить исключение std::length_error.

Ещё одним наследником класса std::exception является класс std::runtime_error − исключение времени выполнения. Предполагается, что такие исключения достаточно сложно обнаружить заранее, до того как программа начала работать. В стандартной библиотеке имеются несколько наследников класса std::runtime_error − std::range_error, std::overflow_error, std::underflow_error, std::system_error. Лишь небольшое количество редко используемых функций стандартной библиотеки генерируют исключения данных классов, поэтому примеры мы не приводим. Обычные арифметические операции таких исключений не возбуждают. Однако, можно использовать данные исключения в своих программах − например, при разработке библиотеки по работе с простыми дробями может быть вполне уместно возбуждение исключений типов std::overflow_error или std::range_error.

Иногда может быть удобно создание своего класса − наследника от одного из стандартных классов исключений, например:

// Пример 4.9 - наследование исключения

class FileNotOpenedException : public std::runtime_error{

public:

FileNotOpenedException(const std::string &fileName)

: std::runtime_error ("Ошибка открытия файла " + fileName) {

}

// функция what будет возвращать строку,

// которую мы передали в конструктор предка

};

Здесь удобно то, что класс std::runtime_error содержит конструктор, принимающий в качестве параметра строку с описанием ошибки, а также реализацию функции what, возвращающую эту строку. В результате нет необходимости писать аналогичную реализацию "с нуля".