Выбрать главу

В главе 11 были введены выражения повторного возбуждения исключения, которые используются в catch-обработчике для передачи исключения какому-то другому обработчику выше в цепочке вызовов. Такое выражение имеет вид

throw;

Как ведет себя эта инструкция, если она расположена в catch-обработчике исключений базового класса? Например, каким будет тип повторно возбужденного исключения, если mathFunc() возбуждает исключение типа divideByZero?

void calculate( int parm ) {

try {

mathFunc( parm ); // возбуждает исключение divideByZero

}

catch ( mathExcp mExcp ) {

// частично обрабатывает исключение

// и генерирует объект-исключение еще раз

throw;

}

}

Будет ли повторно возбужденное исключение иметь тип divideByZero –тот же, что и исключение, возбужденное функцией mathFunc()? Или тип mathExcp, который указан в объявлении исключения в catch-обработчике?

Напомним, что выражение throw повторно генерирует исходный объект-исключение. Так как исходный объект имеет тип divideByZero, то повторно возбужденное исключение будет такого же типа. В catch-обработчике объект mExcp инициализируется копией подобъекта объекта типа divideByZero, который соответствует его базовому классу MathExcp. Доступ к ней осуществляется только внутри catch-обработчика, она не является исходным объектом-исключением, который повторно генерируется.

Предположим, что классы в нашей иерархии исключений имеют деструкторы:

class pushOnFull {

public:

pushOnFull( int i ) : _value( i ) { }

int value() { return _value; }

~pushOnFull(); // вновь объявленный деструктор

private:

int _value;

};

Когда они вызываются? Чтобы ответить на этот вопрос, рассмотрим catch-обработчик:

catch ( pushOnFull eObj ) {

cerr "попытка поместить значение " eObj.value()

" в полный стек\n";

}

Поскольку в объявлении исключения eObj объявлен как локальный для catch-обработчика объект, а в классе pushOnFull есть деструктор, то eObj уничтожается при выходе из обработчика. Когда же вызывается деструктор для объекта-исключения, созданного в момент возбуждения исключения, – при входе в catch-обработчик или при выходе из него? Однако уничтожать исключение в любой из этих точек может быть слишком рано. Можете сказать, почему? Если catch-обработчик возбуждает исключение повторно, передавая его выше по цепочке вызовов, то уничтожать объект-исключение нельзя до момента выхода из последнего catch-обработчика.

19.2.4. Объекты-исключения и виртуальные функции

Если сгенерированный объект-исключение имеет тип производного класса, а обрабатывается catch-обработчиком для базового, то этот обработчик не может использовать особенности производного класса. Например, к функции-члену value(), которая объявлена в классе pushOnFull, нельзя обращаться в catch-обработчике Excp:

catch ( const Excp &eObj ) {

// ошибка: в классе Excp нет функции-члена value()

cerr "попытка поместить значение " eObj.value()

" в полный стек\n";

}

Но мы можем перепроектировать иерархию классов исключений и определить виртуальные функции, которые можно вызывать из catch-обработчика для базового класса Excp с целью получения доступа к функциям-членам более специализированного производного:

// новые определения классов, включающие виртуальные функции

class Excp {

public:

virtual void print( string msg ) {

cerr "Произошло исключение"

endl;

}

};

class stackExcp : public Excp { };

class pushOnFull : public stackExcp {

public:

virtual void print() {

cerr "попытка поместить значение " _value

" в полный стек\n";

}

// ...

};

Функцию print() теперь можно использовать в catch-обработчике следующим образом:

int main() {

try {

// iStack::push() возбуждает исключение pushOnFull

} catch ( Excp eObj ) {

eObj.print(); // хотим вызвать виртуальную функцию,

// но вызывается экземпляр из базового класса

}

}

Хотя возбужденное исключение имеет тип pushOnFull, а функция print() виртуальна, инструкция eObj.print() печатает такую строку:

Произошло исключение

Вызываемая print() является членом базового класса Excp, а не замещает ее в производном. Но почему?

Вспомните, что объявление исключения в catch-обработчике ведет себя почти так же, так объявление параметра. Когда управление попадает в catch-обработчик, то, поскольку в нем объявлен объект, а не ссылка, eObj инициализируется копией подобъекта Excp базового класса объекта исключения. Поэтому eObj – это объект типа Excp, а не pushOnFull. Чтобы вызвать виртуальные функции из производных классов, в объявлении исключения должен быть указатель или ссылка: