|
|
Создатель библиотеки способен обнаружить динамические ошибки, но не представляет какой в общем случае должна быть реакция на них. Пользователь библиотеки способен написать реакцию на такие ошибки, но не в силах их обнаружить. Если бы он мог, то сам разобрался бы с ошибками в своей программе, и их не пришлось бы выявлять в библиотечных функциях. Для решения этой проблемы в язык введено понятие особой ситуации *.
* Только недавно комитетом по стандартизации С++ особые ситуации были включены в стандарт языка, но на время написания этой книги они еще не вошли в большинство реализаций.
Суть этого понятия в том, что функция, которая обнаружила ошибку и не
может справиться с нею, запускает особую ситуацию, рассчитывая, что
устранить проблему можно в той функции, которая прямо или опосредованно
вызывала первую. Если функция рассчитана на обработку ошибок некоторого
вида, она может указать это явно, как готовность перехватить данную
особую ситуацию.
Рассмотрим в качестве примера как для класса Vector можно
представлять и обрабатывать особые ситуации, вызванные выходом за
границу массива:
class Vector { int* p; int sz; public: class Range { }; // класс для особой ситуации int& operator[](int i); // ... };
Предполагается, что объекты класса Range будут использоваться как особые ситуации, и запускать их можно так:
int& Vector::operator[](int i) { if (0<=i && i<sz) return p[i]; throw Range(); }
Если в функции предусмотрена реакция на ошибку недопустимого значения индекса, то ту часть функции, в которой эти ошибки будут перехватываться, надо поместить в оператор try. В нем должен быть и обработчик особой ситуации:
void f(Vector& v) { // ... try { do_something(v); // содержательная часть, работающая с v } catch (Vector::Range) { // обработчик особой ситуации Vector::Range // если do_something() завершится неудачно, // нужно как-то среагировать на это // сюда мы попадем только в том случае, когда // вызов do_something() приведет к вызову Vector::operator[]() // из-за недопустимого значения индекса } // ... }
Обработчиком особой ситуации называется конструкция
catch ( /* ... */ ) {
// ...
}
Ее можно использовать только сразу после блока, начинающегося служебным словом try, или сразу после другого обработчика особой ситуации. Служебным является и слово catch. После него идет в скобках описание, которое используется аналогично описанию формальных параметров функции, а именно, в нем задается тип объектов, на которые рассчитан обработчик, и, возможно, имена параметров (см. $$9.3). Если в do_something() или в любой вызванной из нее функции произойдет ошибка индекса (на любом объекте Vector), то обработчик перехватит особую ситуацию и будет выполняться часть, обрабатывающая ошибку. Например, определения следующих функций приведут к запуску обработчика в f():
void do_something() { // ... crash(v); // ... } void crash(Vector& v) { v[v.size()+10]; // искусственно вызываем ошибку индекса }
Процесс запуска и перехвата особой ситуации предполагает просмотр
цепочки вызовов от точки запуска особой ситуации до функции, в которой
она перехватывается. При этом восстанавливается состояние стека,
соответствующее функции, перехватившей ошибку, и при проходе по всей
цепочке вызовов для локальных объектов функций из этой цепочки вызываются
деструкторы. Подробно это описано в $$9.4.
Если при просмотре всей цепочки вызовов, начиная с запустившей
особую ситуацию функции, не обнаружится подходящий обработчик, то
программа завершается. Подробно это описано в $$9.7.
Если обработчик перехватил особую ситуацию, то она будет обрабатываться
и другие, рассчитанные на эту ситуацию, обработчики не будут
рассматриваться. Иными словами, активирован будет только тот обработчик,
который находится в самой последней вызывавшейся функции, содержащей
соответствующие обработчики. В нашем примере функция f() перехватит
Vector::Range, поэтому эту особую ситуацию нельзя перехватить ни в
какой вызывающей f() функции:
int ff(Vector& v) { try { f(v); // в f() будет перехвачена Vector::Range } catch (Vector::Range) { // значит сюда мы никогда не попадем // ... } }
| |