|
Если для обработки особых ситуаций мы используем иерархию классов, то, естественно, каждый обработчик должен разбираться только с частью информации, передаваемой при особых ситуациях. Можно сказать, что, как правило, особая ситуация перехватывается обработчиком ее базового класса, а не обработчиком класса, соответствующего именно этой особой ситуации. Именование и перехват обработчиком особой ситуации семантически эквивалентно именованию и получению параметра в функции. Проще говоря, формальный параметр инициализируется значением фактического параметра. Это означает, что запущенная особая ситуация "низводится" до особой ситуации, ожидаемой обработчиком. Например:
class Matherr { // ... virtual void debug_print(); }; class Int_overflow : public Matherr { public: char* op; int opr1, opr2; Int_overflow(const char* p, int a, int b) { cerr << op << '(' << opr1 << ',' << opr2 << ')'; } }; void f() { try { g(); } catch (Matherr m) { // ... } }
При входе в обработчик Matherr особая ситуация m является объектом
Matherr, даже если при обращении к g() была запущена Int_overflow.
Это означает, что дополнительная информация, передаваемая в
Int_overflow, недоступна.
Как обычно, чтобы иметь доступ к дополнительной информации можно
использовать указатели или ссылки. Поэтому можно было написать так:
int add(int x, int y) // сложить x и y с контролем { if (x > 0 && y > 0 && x > MAXINT - y || x < 0 && y < 0 && x < MININT + y) throw Int_overflow("+", x, y); // Сюда мы попадаем, либо когда проверка // на переполнение дала отрицательный результат, // либо когда x и y имеют разные знаки return x + y; } void f() { try { add(1,2); add(MAXINT,-2); add(MAXINT,2); // а дальше - переполнение } catch (Matherr& m) { // ... m.debug_print(); } }
Здесь последнее обращение к add приведет к запуску особой ситуации,
который, в свою очередь, приведет к вызову Int_overflow::debug_print().
Если бы особая ситуация передавалась по значению, а не по ссылке, то
была бы вызвана функция Matherr::debug_print().
Нередко бывает так, что перехватив особую ситуацию, обработчик
решает, что с этой ошибкой он ничего не сможет поделать. В таком
случае самое естественное запустить особую ситуацию снова в надежде,
что с ней сумеет разобраться другой обработчик:
void h() { try { // какие-то операторы } catch (Matherr) { if (can_handle_it) { // если обработка возможна, // сделать ее } else { throw; // повторный запуск перехваченной // особой ситуации } } }
Повторный запуск записывается как оператор throw без параметров. При этом снова запускается исходная особая ситуация, которая была перехвачена, а не та ее часть, на которую рассчитан обработчик Matherr. Иными словами, если была запущена Int_overflow, вызывающая h() функция могла бы перехватить ее как Int_overflow, несмотря на то, что она была перехвачена в h() как Matherr и запущена снова:
void k() { try { h(); // ... } catch (Int_overflow) { // ... } }
Полезен вырожденный случай перезапуска. Как и для функций, эллипсис ... для обработчика означает "любой параметр", поэтому оператор catch (...) означает перехват любой особой ситуации:
void m() { try { // какие-то операторы } catch (...) { // привести все в порядок throw; } }
Этот пример надо понимать так: если при выполнении основной части
m() возникает особая ситуация, выполняется обработчик, которые
выполняет общие действия по устранению последствий особой ситуации,
после этих действий особая ситуация, вызвавшая их, запускается
повторно.
Поскольку обработчик может перехватить производные особые ситуации
нескольких типов, порядок, в котором идут обработчики в проверяемом
блоке, существенен. Обработчики пытаются перехватить особую
ситуацию в порядке их описания. Приведем пример:
try { // ... } catch (ibuf) { // обработка переполнения буфера ввода } catch (io) { // обработка любой ошибки ввода-вывода } catch (stdlib) { // обработка любой особой ситуации в библиотеке } catch (...) { // обработка всех остальных особых ситуаций }
Тип особой ситуации в обработчике соответствует типу запущенной
особой ситуации в следующих случаях: если эти типы совпадают, или
второй тип является типом доступного базового класса запущенной ситуации,
или он является указателем на такой класс, а тип ожидаемой ситуации
тоже указатель ($$R.4.6).
Поскольку транслятору известна иерархия классов, он способен
обнаружить такие нелепые ошибки, когда обработчик catch (...) указан
не последним, или когда обработчик ситуации базового класса
предшествует обработчику производной от этого класса ситуации
($$R.15.4). В обоих случая, последующий обработчик (или обработчики)
не могут быть запущены, поскольку они "маскируются" первым
обработчиком.