13.2 Реализация — Exception

Предыдущая  Содержание  Следующая V*D*V

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() была применена к верхнему элементу на стеке исключений.

 

Предыдущая  Содержание  Следующая