С++ для начинающих


Иерархия классов исключений в стандартной библиотеке 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 );

   };

}

Функция может возбудить исключение 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().

Упражнение 19.5

Какие исключения могут возбуждать следующие функции:

#include <stdexcept>

(a) void operate() throw( logic_error );

(b) int mathErr( int ) throw( underflow_error, overflow_error );

(c) char manip( string ) throw( );

Упражнение 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().


Содержание раздела