Бьерн Страуструп - Язык программирования С++. Главы 8-10 - Обработка ошибок

ОГЛАВЛЕНИЕ

9.1 Обработка ошибок

Создатель библиотеки способен обнаружить динамические ошибки, но не представляет какой в общем случае должна быть реакция на них. Пользователь библиотеки способен написать реакцию на такие ошибки, но не в силах их обнаружить. Если бы он мог, то сам разобрался бы с ошибками в своей программе, и их не пришлось бы выявлять в библиотечных функциях. Для решения этой проблемы в язык введено понятие особой ситуации. Только недавно комитетом по стандартизации С++ особые ситуации были включены в стандарт языка, но на время написания этой книги они еще не вошли в большинство реализаций. Суть этого понятия в том, что функция, которая обнаружила ошибку и не может справиться с нею, запускает особую ситуацию, рассчитывая, что устранить проблему можно в той функции, которая прямо или опосредованно вызывала первую. Если функция рассчитана на обработку ошибок некоторого вида, она может указать это явно, как готовность перехватить данную особую ситуацию.

Рассмотрим в качестве примера как для класса 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) { // значит сюда мы никогда не попадем
               // ...
            }
          }