Иерархия классов исключений в стандартной библиотеке C++
В начале этого раздела мы определили иерархию классов исключений, с помощью которой наша программа сообщает об аномальных ситуациях. В стандартной библиотеке C++ есть аналогичная иерархия, предназначенная для извещения о проблемах при выполнении функций из самой стандартной библиотеки. Эти классы исключений вы можете использовать в своих программах непосредственно или создать производные от них классы для описания собственных специфических исключений.
Корневой класс исключения в стандартной иерархии называется exception. Он определен в стандартном заголовочном файле <exception> и является базовым для всех исключений, возбуждаемых функциями из стандартной библиотеки. Класс exception
имеет следующий интерфейс:
namespace std { class exception public: exception() throw(); exception( const exception & ) throw(); exception& operator=( const exception & ) throw(); virtual ~exception() throw(); virtual const char* what() const throw(); }; |
}
Как и всякий другой класс из стандартной библиотеки C++, exception помещен в пространство имен std, чтобы не засорять глобальное пространство имен программы.
Первые четыре функции-члена в определении класса – это конструктор по умолчанию, копирующий конструктор, копирующий оператор присваивания и деструктор. Поскольку все они открыты, любая программа может свободно создавать и копировать объекты-исключения, а также присваивать им значения. Деструктор объявлен виртуальным, чтобы сделать возможным дальнейшее наследование классу exception.
Самой интересной в этом списке является виртуальная функция what(), которая возвращает C-строку с текстовым описанием возбужденного исключения. Классы, производные от exception, могут заместить what()
собственной версией, которая лучше характеризует объект-исключение.
Отметим, что все функции в определении класса exception имеют пустую спецификацию throw(), т.е. не возбуждают никаких исключений. Программа может манипулировать объектами-исключениями (к примеру, внутри catch-обработчиков типа exception), не опасаясь, что функции создания, копирования и уничтожения этих объектов возбудят исключения.
Помимо корневого exception, в стандартной библиотеке есть и другие классы, которые допустимо использовать в программе для извещения об ошибках, обычно подразделяемых на две больших категории: логические ошибки и ошибки времени выполнения.
Логические ошибки обусловлены нарушением внутренней логики программы, например логических предусловий или инвариантов класса. Предполагается, что их можно найти и предотвратить еще до начала выполнения программы. В стандартной библиотеке определены следующие такие ошибки:
namespace std { class logic_error : public exception { // логическая ошибка public: explicit logic_error( const string &what_arg ); }; class invalid_argument : public logic_error { // неверный аргумент public: explicit invalid_argument( const string &what_arg ); }; class out_of_range : public logic_error { // вне диапазона public: explicit out_of_range( const string &what_arg ); }; class length_error : public logic_error { // неверная длина public: explicit length_error( const string &what_arg ); }; class domain_error : public logic_error { // вне допустимой области public: explicit domain_error( const string &what_arg ); }; |
Функция может возбудить исключение invalid_argument, если получит аргумент с некорректным значением; в конкретной ситуации, когда значение аргумента выходит за пределы допустимого диапазона, разрешается возбудить исключение out_of_range, а length_error
используется для оповещения о попытке создать объект, длина которого превышает максимально возможную.
Ошибки времени выполнения, напротив, вызваны событием, с самой программой не связанным. Предполагается, что их нельзя обнаружить, пока программа не начала работать. В стандартной библиотеке определены следующие такие ошибки:
namespace std { class runtime_error : public exception { // ошибка времени выполнения public: explicit runtime_error( const string &what_arg ); }; class range_error : public runtime_error { // ошибка диапазона public: explicit range_error( const string &what_arg ); }; class overflow_error : public runtime_error { // переполнение public: explicit overflow_error( const string &what_arg ); }; class underflow_error : public runtime_error { // потеря значимости public: explicit underflow_error( const string &what_arg ); }; |
Упражнение 19.5
Какие исключения могут возбуждать следующие функции:
#include <stdexcept> (a) void operate() throw( logic_error ); (b) int mathErr( int ) throw( underflow_error, overflow_error ); |
Упражнение 19.6
Объясните, как механизм обработки исключений в C++ поддерживает технику программирования “захват ресурса – это инициализация; освобождение ресурса – это уничтожение”.
Упражнение 19.7
Исправьте ошибку в списке catch-обработчиков для данного try-блока:
#include <stdexcept> int main() { try { // использование функций из стандартной библиотеки } catch( exception ) { } catch( runtime_error &re ) { } catch( overflow_error eobj ) { } |
Упражнение 19.8
Дана программа на C++:
int main() { // использование стандартной библиотеки |
Модифицируйте main()
так, чтобы она перехватывала все исключения, возбуждаемые функциями стандартной библиотеки. Обработчики должны печатать сообщение об ошибке, ассоциированное с исключением, а затем вызывать функцию abort() (она определена в заголовочном файле <cstdlib>) для завершения main().
}
Функция может возбудить исключение range_error, чтобы сообщить об ошибке во внутренних вычислениях. Исключение overflow_error
говорит об ошибке арифметического переполнения, а underflow_error – о потере значимости.
Класс exception
является базовым и для класса исключения bad_alloc, которое возбуждает оператор new(), когда ему не удается выделить запрошенный объем памяти (см. раздел 8.4), и для класса исключения bad_cast, возбуждаемого в ситуации, когда ссылочный вариант оператора dynamic_cast не может быть выполнен (см. раздел 19.1).
Переопределим оператор operator[] в шаблоне Array из раздела 16.12 так, чтобы он возбуждал исключение типа range_error, если индекс массива Array
выходит за границы:
#include <stdexcept> #include <string> template <class elemType> class Array { public: // ... elemType& operator[]( int ix ) const { if ( ix < 0 || ix >= _size ) { string eObj = "ошибка: вне диапазона в Array<elemType>::operator[]()"; throw out_of_range( eObj ); } return _ia[ix]; } // ... private: int _size; elemType *_ia; |
Для использования предопределенных классов исключений в программу необходимо включить заголовочный файл <stdexcept>. Описание возбужденного исключения содержится в объекте eObj
типа string. Эту информацию можно извлечь в обработчике с помощью функции-члена what():
int main() { try { // функция main() такая же, как в разделе 16.2 } catch ( const out_of_range &excep ) { // печатается: // ошибка: вне диапазона в Array<elemType>::operator[]() cerr << excep.what() << "\n"; return -1; } |
В данной реализации выход индекса за пределы массива в функции try_array() приводит к тому, что оператор взятия индекса operator[]()
класса Array
возбуждает исключение типа out_of_range, которое перехватывается в main().