Спецификации исключений
С помощью спецификации исключений (см. раздел 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().