На этом шаге мы закончим рассматривать обработку исключений.
Как уже показано, язык C++ позволяет описывать набор исключений, которые может порождать функция. Это описание исключений помещается в качестве суффикса в определении функции или в ее прототипе. Синтаксис такого описания исключений следующий:
throw (список_идентификаторов_типов)
где список_идентификаторов_типов - это один идентификатор типа или последовательность разделенных запятыми идентификаторов типов. Указанный суффикс, определяющий генерируемые функцией исключения, не входит в тип функции. Поэтому при описании указателей на функцию этот суффикс не используется. При описании указателя на функцию задают лишь возвращаемое функцией значение и типы аргументов.
Примеры прототипов функций с указанием генерируемых исключений:
void f2(void) throw(); //Функция, не порождающая исключений. void f3(void) throw(BETA); //Функция может порождать только исключение типа BETA. void (*fptr)(); // Указатель на функцию, возвращающую void. fptr = f2; // Корректное присваивание. fptr = f3; // Корректное присваивание.
В следующих примерах описываются еще некоторые функции с перечислением исключений:
void f1(); // Может порождать любые исключения. void f2() throw(); // Не порождает никаких исключений. void f3() throw(А,В*); // Может порождать исключения в виде // объектов классов, порожденных из A // или указателей на объекты классов, // наследственно порожденных из В.
Если функция порождает исключение, не указанное в списке, программа вызывает функцию unexpected(). Это происходит во время выполнения программы и не может быть выяснено на стадии ее компиляции. Поэтому необходимо внимательно описывать процедуры обработки тех исключений, которые порождаются функциями, вызываемыми изнутри (из тела рассматриваемой) функции.
Особое внимание необходимо обратить на перегрузку виртуальных функций тех классов, к которым относятся исключения. Речь идет о следующем. Пусть классы ALPHA и BETA определены следующим образом:
class ALPHA // Базовый класс для BETA. { public: virtual void print(void) { cout << "print: Класс ALPHA"; } }; class BETA: public ALPHA { public: virtual void print(void) { cout << "print: Класс BETA"; } }; BETA b; // Создан объект класса BETA.
Теперь рассмотрим три ситуации:
... try { throw(b); // Исключение в виде объекта класса BETA. } catch (ALPHA d) { d.print(); } ...
... try { throw(b); // Исключение в виде объекта класса BETA. } catch (ALPHA & d) { d.print(); } ...
... try { throw(b); // Исключение в виде объекта класса BETA. } catch (BETA d) { d.print() ; }
В первом случае при входе в обработчик фактический параметр, соответствующий формальному параметру ALPHA d, воспринимается как объект типа ALPHA, даже если исключение создано как объект класса BETA. Поэтому при обработке доступны только компоненты класса ALPHA. Результатом выполнения этого фрагмента будет печать строки:
print: Класс ALPHA
Во втором случае во избежание потери информации использована передача значения по ссылке. В этом случае будет вызвана компонентная функция print() класса BETA, и результат будет таким:
print: Класс BETA
Попутно отметим, что функция print() класса BETA будет вызываться и в том случае, если она будет являться защищенным (protected) или собственным (private) компонентом класса BETA. Так, если в описании класса BETA вместо ключевого слова public поставить protected или private, то результат не изменится. В этом нет ничего удивительного, так как права доступа к виртуальной функции определяются ее определением и не заменяются на права доступа к функциям, которые позднее переопределяют ее. Поэтому и в данном случае права доступа к функции print() определяются правами, заданными в классе ALPHA.
Конечно, можно непосредственно "отлавливать" исключение в виде объекта класса BETA, как показано в третьем примере. Однако в этом случае если функция print() будет входить в число защищенных или собственных компонентов класса BETA, такой вызов функции print() окажется невозможным, и при компиляции будет выдано сообщение об ошибке.
На следующем шаге мы рассмотрим обработку исключений при динамическом выделении памяти.