13.2 Реализация — Exception |
Предыдущая Содержание Следующая |
Exception — класс, который обеспечивает допускающую вложения обработку исключений. Объекты Exception должны удаляться порядке, обратном их созданию. Обычно новейший объект представляет обработчик ошибок, который получает управление от вызова cause().
// new(Exception())
#include <setjmp.h>
void cause (int number); // если установлен, идём в catch()
% Class Exception: Object { int armed; // устанавливается catch() jmp_buf label; // используется catch() % void * catchException (_self); %}
new(Exception()) создаёт объект исключения, который вталкивается в скрытый стек, хранящий все такие объекты:
#include "List.h"
static void * stack;
% Exception ctor { void * self = super_ctor(Exception(), _self, app);
if (! stack) stack = new(List(), 10); addLast(stack, self); return self; }
Для реализации стека исключений мы используем объект List из раздела 7.7. Объекты исключений должны удаляться в порядке точно противоположном созданию. Из стека их выталкивает деструктор:
% Exception dtor { void * top; %casts assert(stack); top = takeLast(stack); assert(top == self); return super_dtor(Exception(), self); }
Исключение создаётся вызовом cause() с ненулевым аргументом, кодом исключения. Если возможно, cause() выполнит longjmp() к объекту исключения наверху стека, то есть к самому последнему созданному такому объекту. Обратите внимание на то, что cause() может вернуться в место вызова, а может и нет.
void cause (int number) { unsigned cnt;
if (number && stack && (cnt = count(stack))) { void * top = lookAt(stack, cnt-1); struct Exception * e = cast(Exception(), top);
if (e -> armed) longjmp(e -> label, number); } }
cause() является функцией, не методом. Однако, она реализована как часть реализации Exception и действительно имеет доступ к внутренним данным этого класса. Такая функция часто упоминается как дружественная классу. cause() принимает много мер предосторожности: параметр не должен быть нулём; должен существовать стек исключений и он должен содержать объекты, а объект наверху стека должен быть исключением; и наконец, объект исключения должен быть готов принять исключение. Если какая-либо проверка терпит неудачу, cause() возвращается и вызвавший её метод должен справиться с ситуацией. Прежде, чем объект исключения сможет использоваться, он должен быть снаряжён, то есть информация jmp_buf должна быть сохранена с помощью setjmp() до того, как объект будет использоваться cause(). Создание и настройка объекта это две отдельных операции по нескольким причинам. Объект обычно создаётся с помощью new() и объект — результат этой операции. Объект исключения должен снаряжаться setjmp(), а эта функция вернёт два целочисленных значения: в первый раз ноль, а во второй раз значение для передачи в longjmp(). Трудно представить, как можно было бы объединить эти две операции. Что ещё более важно, ANSI-C вводит серьёзные ограничения на то, где можно вызывать setjmp(). Это должно быть в значительной степени определено только как выражение и это выражение может использоваться только в качестве оператора или управлять оператором цикла или выбора. Изящным решением для снаряжения объекта исключения является следующий макрос, определенный в Exception.d:
#define catch(e) setjmp(catchException(e))
catch() используется там, где обычно применялся бы setjmp(), то есть ограничения, налагаемые ANSI-C для setjmp(), могут соблюдаться для catch(). Позже значение, переданное cause(), будет возвращено. Трюк основан на вызове метода catchException(), чтобы предоставить параметр для setjmp():
% catchException { %casts self -> armed = 1; return self -> label; }
catchException() просто устанавливает .armed и возвращает jmp_buf, чтобы setjmp() могла его проинициализировать. Согласно стандарту ANSI-C, jmp_buf имеет тип "массив", то есть имя jmp_buf представляет собой адрес. Если бы система C ошибочно определила jmp_buf как структуру, мы должны были бы просто возвратить её адрес явным образом. Мы не требуем, чтобы catch() была применена к верхнему элементу на стеке исключений.
|
Предыдущая Содержание Следующая |