int main() {
try {
// тело функции main()
}
catch ( pushOnFull ) {
// ...
}
catch ( popOnEmpty ) {
// ...
}
Функциональный try-блок ассоциирует группу catch-обработчиков с телом функции. Если инструкция внутри тела возбуждает исключение, то поиск его обработчика ведется среди тех, что следуют за телом функции.
Функциональный try-блок необходим для конструкторов класса. Почему? Определение конструктора имеет следующий вид:
имя_класса( список_параметров )
// список инициализации членов:
: член1(выражение1 ) , // инициализация член1
член2(выражение2 ) , // инициализация член2
// тело функции:
{ /* ... */ }
выражение1 и выражение2 могут быть выражениями любого вида, в частности функциями, которые возбуждают исключения.
Рассмотрим еще раз класс Account, описанный в главе 14. Его конструктор можно переопределить так:
nline Account::
Account( const char* name, double opening_bal )
: _balance( opening_bal - ServiceCharge() )
{
_name = new char[ strlen(name) + 1 ];
strcpy( _name, name );
_acct_nmbr = get_unique_acct_nmbr();
}
Функция ServiceCharge(), вызываемая для инициализации члена _balance, может возбуждать исключение. Как нужно реализовать конструктор, если мы хотим обрабатывать все исключения, возбуждаемые функциями, которые вызываются при конструировании объекта типа Account?
Помещать try-блок в тело функции нельзя:
inline Account::
Account( const char* name, double opening_bal )
: _balance( opening_bal - ServiceCharge() )
{
try {
_name = new char[ strlen(name) + 1 ];
strcpy( _name, name );
_acct_nmbr = get_unique_acct_nmbr();
}
catch (...) {
// специальная обработка
// не перехватывает исключения,
// возбужденные в списке инициализации членов
}
}
Поскольку try-блок не охватывает список инициализации членов, то catch-обработчик, находящийся в конце конструктора, не рассматривается при поиске кандидатов, которые способны перехватить исключение, возбужденное в функции ServiceCharge().
Использование функционального try-блока – это единственное решение, гарантирующее, что все исключения, возбужденные при создании объекта, будут перехвачены в конструкторе. Для конструктора класса Account такой try-блок можно определить следующим образом:
inline Account::
Account( const char* name, double opening_bal )
try
: _balance( opening_bal - ServiceCharge() )
{
_name = new char[ strlen(name) + 1 ];
strcpy( _name, name );
_acct_nmbr = get_unique_acct_nmbr();
}
catch (...) {
// теперь специальная обработка
// перехватывает исключения,
// возбужденные в ServiceCharge()
}
Обратите внимание, что ключевое слово try находится перед списком инициализации членов, а составная инструкция, образующая try-блок, охватывает тело конструктора. Теперь предложение catch(...) принимается во внимание при поиске обработчика исключения, возбужденного как в списке инициализации членов, так и в теле конструктора.
19.2.8. Иерархия классов исключений в стандартной библиотеке C++
В начале этого раздела мы определили иерархию классов исключений, с помощью которой наша программа сообщает об аномальных ситуациях. В стандартной библиотеке C++ есть аналогичная иерархия, предназначенная для извещения о проблемах при выполнении функций из самой стандартной библиотеки. Эти классы исключений вы можете использовать в своих программах непосредственно или создать производные от них классы для описания собственных специфических исключений.
Корневой класс исключения в стандартной иерархии называется 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.