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


Спецификации исключений


С помощью спецификации исключений (см. раздел 11.4) в объявлении функции указывается множество исключений, которые она может возбуждать прямо или косвенно. Спецификация позволяет гарантировать, что функция не возбудит не перечисленные в ней исключения.

Такую спецификацию разрешается задавать для функций-членов класса так же, как и для обычных функций; она должна следовать за списком параметров функции-члена. Например, в определении класса bad_alloc из стандартной библиотеки C++ функции-члены имеют пустую спецификацию исключений throw(), т.е. гарантированно не возбуждают никаких исключений:

class bad_alloc : public exception {

   // ...

public:

   bad_alloc() throw();

   bad_alloc( const bad_alloc & ) throw();

   bad_alloc & operator=( const bad_alloc & ) throw();

   virtual ~bad_alloc() throw();

   virtual const char* what() const throw();

};

Отметим, что если функция-член объявлена с модификатором const или volatile, как, скажем, what() в примере выше, то спецификация исключений должна идти после него.

Во всех объявлениях одной и той же функции спецификации исключений обязаны содержать одинаковые типы. Если речь идет о функции-члене, определение которой находится вне определения класса, то спецификации исключений в этом определении и в объявлении функции должны совпадать:



#include <stdexcept>

// <stdexcept> определяет класс overflow_error

class transport {

   // ...

public:

   double cost( double, double ) throw ( overflow_error );

   // ...

};

// ошибка: спецификация исключений отличается от той, что задана

//         в объявлении в списке членов класса

double transport::cost( double rate, double distance ) { }

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

class Base {

public:

   virtual double f1( double ) throw();


   virtual int f2( int ) throw( int );

   virtual string f3() throw( int, string );

   // ...

}

class Derived : public Base {

public:

   // ошибка: спецификация исключений накладывает меньше ограничений,

   //         чем на Base::f1()

   double f1( double ) throw( string );

   // правильно: та же спецификация исключений, что и для Base::f2()

   int f2( int ) throw( int );

   // правильно: спецификация исключений f3() накладывает больше ограничений

   string f3( ) throw( int );

   // ...

};

Почему спецификация исключений в производном классе должна накладывать не меньше ограничений, чем в базовом? В этом случае мы можем быть уверены, что вызов виртуальной функции из производного класса по указателю на тип базового не нарушит спецификацию исключений функции-члена базового класса:

// гарантируется, что исключения возбуждены не будут

void compute( Base *pb ) throw()

{

   try {

      pb->f3( );  // может возбудить исключение типа int или string

   }

   // обработка исключений, возбужденных в Base::f3()

   catch ( const string & ) { }

   catch ( int ) { }

}

Объявление f3() в классе Base гарантирует, что эта функция возбуждает лишь исключения типа int или string. Следовательно, функция compute() включает catch-обработчики только для них. Поскольку спецификация исключений f3() в производном классе Derived накладывает больше ограничений, чем в базовом Base, то при программировании в согласии с интерфейсом класса Base наши ожидания не будут обмануты.

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

class stackExcp : public Excp { };

class popObEmpty : public stackExcp { };

class pushOnFull : public stackExcp { };

void stackManip() throw( stackExcp )

{

   // ...

}

Спецификация исключений указывает, что stackManip() может возбуждать исключения не только типа stackExcp, но также popOnEmpty и pushOnFull. Напомним, что класс, открыто наследующий базовому, представляет собой пример отношения ЯВЛЯЕТСЯ, т.е. является

частным случае более общего базового класса. Поскольку popOnEmpty и pushOnFull – частные случаи stackExcp, они не нарушают спецификации исключений функции stackManip().


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