Справочное руководство по C++

Страустрап Бьярн

R.15 Обработка особых ситуаций

 

 

R.15.1 Обработка особых ситуаций

При обработке особых ситуаций в ходе выполнения программы информация и управление передаются из некоторой точки обработчику особых ситуаций. Обработчик находится в цепочке выполненных вызовов функций. Управление обработчику передается с помощью выражения-запуска, которое может быть только в проверяемом-блоке обработчика или в функции, вызванной из проверяемого-блока.

проверяемый-блок:

 try составной-оператор список-обработчиков

список-обработчиков:

 обработчик список-обработчиков opt

обработчик:

 catch ( описание-особой-ситуации ) составной-оператор

описание-особой-ситуации:

 список-спецификаций-типа описатель

 список-спецификаций-типа абстрактный-описатель

 список-спецификаций-типа

 …

выражение-запуска:

 throw выражение opt

Конструкция проверяемый-блок является оператором (§R.6), а выражение-запуска - унарным выражением типа void (§R.5). Иногда выражение-запуска называют "точкой запуска", а про функцию, в которой встретилось выражение-запуска, говорят, что она "запускает особую ситуацию. Часть программы, которой передается управление из точки запуска называется обработчиком.

 

R.15.2 Запуск особой ситуации

При запуске особой ситуации управление передается обработчику. Запуск сопровождается передачей объект, тип которого определяет, какой обработчик должен перехватить особую ситуацию. Так, выражение

throw "Help!";

может быть перехвачено некоторым обработчиком с типом char*:

try {

 //…

}

catch(const char* p) {

 // здесь обрабатывается особая ситуация в символьных строках

}

а особая ситуация Overflow (переполнение):

class Overflow {

 //…

public:

 Overflow(char,double,double);

};

void f(double x)

{

 //…

 throw Overflow('+',x,3.45e107);

}

может быть перехвачена обработчиком

try {

 //…

 f(1.2);

 //…

}

catch(Overflow& oo) {

 // здесь обработка особой ситуации типа Overflow

}

При запуске особой ситуации управление передается ближайшему обработчику соответствующего типа. "Ближайший" - это обработчик, проверяемый-блок которого последним получил управление и оно еще не было передано оттуда. Что такое "соответствующий" тип определяется в §R.15.4.

При выполнении выражения-запуска создается временный объект статического типа, который служит операндом в команде throw, Этот объект используется для инициализации переменной, соответствующего типа, описанной в обработчике. Если не считать ограничений на сопоставление типов (см. §R.15.4) и использование временной переменной, то операнд throw аналогичен параметру функции при вызове (§R.5.2.2) или операнду в операторе return.

Если можно было бы, не меняя смысла программы за счет отказа от вызовов конструкторов и деструкторов для временного объекта (§R.12.1), обойтись совсем без временного объекта, то особую ситуацию можно было бы непосредственно инициализировать в обработчике параметром выражения запуска.

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

try {

 //…

}

catch (…) { // перехват всех особых ситуаций

 // (частичная) обработка особых ситуаций

 throw; // передача остальных особых ситуаций другому обработчику

}

 

R.15.3 Конструкторы и деструкторы

Когда управление передается из точки запуска особой ситуации обработчику, то вызываются деструкторы для всех автоматических объектов, построенных с момента входа в проверяемый-блок.

Если объект не был построен полностью, то деструкторы вызываются только для полностью построенных вложенных в него объектов. Кроме того, если особая ситуация запускается в конструкторе при построении элемента автоматического массива, то уничтожаться будут только уже построенные элементы этого массива.

Процесс вызова деструкторов для уничтожения автоматических объектов, построенных в ходе выполнения программы от начала проверяемого-блока до выражения-запуска, называется "раскручиванием стека".

 

R.15.4 Обработка особой ситуации

Обработчик типа T, const T, T& или const& сопоставим с выражением-запуска, имеющим операнд типа E, если:

[1]"The C Programming Language" B. Kernighan, D. Ritchie. Prentice Hall, 1978, 1988. Есть русский перевод: "Язык программирования С. Задачи по языку С" Б. Керниган, Д. Ритчи, А. Фьюер. "Финансы и статистика". 1984
T и E являются одним типом;

[2]American National Standard X3.159-1989. Американский национальный стандарт.
T является доступным (§R.4.6) базовым классом E в точке запуска;

[3] T является типом указателя, а E является таким типом указателя, который можно в точке запуска преобразовать в T с помощью стандартных преобразований указателя (§R.4.6).

Рассмотрим пример:

class Matherr {/*… */ virtual vf(); };

class Overflow: public Matherr {/*… */};

class Underflow: public Matherr {/*… */};

class Zerodivide: public Matherr {/*… */};

void f()

{

 try {

  g();

 }

 catch (Overflow oo) {

  //…

 }

 catch (Matherr mm) {

  //…

 }

}

Здесь обработчик Overflow будет перехватывать ситуации типа Overflow, а обработчик Matherr будет перехватывать ситуации типа Matherr и всех типов, являющихся общими производными от Matherr, включая Underflow и Zerodivide.

Обработчики в проверяемом-блоке подбираются для данной особой ситуации в порядке их описания. Считается ошибкой, если обработчик для базового класса помещен перед обработчиком для производного класса, поскольку при таком расположении управление никогда не попадет к обработчику для производного класса.

Эллипсис … в описании-особой-ситуации действует так же как, и в описании параметров функции, он сопоставим с любой особой ситуацией. Если задан эллипсис, то использующий его обработчик должен идти последним в проверяемом-блоке.

Если в проверяемом-блоке не произошло сопоставления ни с одним из обработчиков, поиск соответствующего обработчика продолжается в динамически объемлющем проверяемом-блоке. Если во всей программе не произошло сопоставления ни с одним обработчиком, вызывается функция terminate() (§R.15.7).

Особая ситуация считается обработанной после входа в тело обработчика. В этот момент завершится "раскручивание стека".

 

R.15.5 Спецификации особых ситуаций

Возникновение и перехватывание особой ситуации влияет на взаимодействие функций. Список особых ситуаций, которые прямо или косвенно может запустить данная функция, можно задать как часть ее описания. Конструкция спецификация-особой-ситуации предшествует описателю функции.

спецификация-особой-ситуации:

 throw ( список-типов opt )

список-типов:

 имя-типа

 список-типов , имя-типа

Приведем пример:

void f() throw (X,Y)

{

 //…

}

Если функция попробует запустить неуказанную в списке ситуацию, управление передается функции unexpected(), см. §R.15.8.

Реализация языка не должна запрещать выражение только потому, что при его вычислении возможен запуск особой ситуации, не указанной в спецификации-особой ситуации описания функции. Обработка непредвиденных особых ситуаций происходит в динамике.

Функция, в которой отсутствует спецификация-особой-ситуации, может запустить любую особую ситуацию.

Функция с пустой спецификацией-особых-ситуаций (throw()) не должна запускать никаких особых ситуаций.

Если функция может запустить особую ситуацию класса X, то она может запустить особую ситуацию любого класса, являющегося общим производным классом от X.

Конструкция спецификация-особой-ситуации не относится к типу функции.

 

R.15.6 Специальные функции

 

Механизм управления особыми ситуациями использует для реакции на ошибки при самой обработке особых ситуаций функции:

terminate() и unexpected().

 

R.15.6.1 Функция terminate()

Иногда от предусмотренной обработки особых ситуаций приходится переходить к более грубым приемам, например:

- когда механизм управления особыми ситуациями не смог найти обработчик для запущенной особой ситуации;

- когда механизм управления особыми ситуациями столкнулся с нарушенной структурой стека;

- когда деструктор, вызванный в процессе раскрутки стека при запуске особой ситуации, сам пытается завершить выполнение программы, запустив особую ситуацию.

В этих случаях вызывается функция

void terminate();

Она в свою очередь вызывает функцию, которая была указана как параметр при последнем обращении к set_terminate():

typedef void(*PFV)();

PFV set_terminate(PFV);

Функция, которая была задана в предыдущем вызове set_terminate(), будет возвращаемым значением текущего вызова. Это помогает пользователю реализовать алгоритм восстановления стека. По умолчанию функция terminate() вызывает abort().

Выбор с помощью terminate() такой функции, которая вместо действительного завершения программы, пытается вернуться в вызвавшую программу, является ошибкой.

 

R.15.6.2 Функция unexpected()

Если функция, имеющая спецификацию-особой-ситуации, запускает неуказанную особую ситуацию, то вызывается функция

void unexpected();

Она в свою очередь вызывает функцию, которая была задана как параметр при последнем обращении к set_unexpected():

typedef void(*PFV)();

PFV set_unexpected(PFV);

Функция, которая была задана в предыдущем вызове set_unexpected(), будет возвращаемым значением текущего вызова. Это помогает пользователю реализовать алгоритм восстановления стека. По умолчанию unexpected() вызывает terminate(). Поскольку по умолчанию terminate() вызывает abort(), результатом будет непосредственное и точное обнаружение ошибки.

 

R.15.7 Особые ситуации и правила доступа

Для формального параметра операции catch действуют такие же правила доступа, как и для формального параметра функции, в которой задана операция catch.

При запуске особой ситуации можно указывать такой объект, который можно копировать и уничтожать в области видимости функции, где задана операция throw.