13.3 Примеры

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

В нашем калькуляторе мы можем заменить явный вызов setjmp() в parse.c на объект исключения:

 

int main (void) {

    volatile int errors = 0;

    char buf [BUFSIZ];

    void * retry = new(Exception());

    ...

    if (catch(retry)) {

        ++ errors;

        reclaim(Node(), delete);

    }

    while (gets(buf))

        ...

 

Порождение исключения перезапустит основной цикл. error() изменяется таким образом, что она завершается порождая исключение:

 

void error (const char * fmt, ...) {

    va_list ap;

 

    va_start(ap, fmt);

    vfprintf(stderr, fmt, ap), putc(’\n’, stderr);

    va_end(ap);

    cause(1);

    assert(0);

}

 

error() вызывается при любой ошибке, обнаруженной в калькуляторе. Она распечатывает сообщение об ошибке и порождает исключение, которое обычно будет непосредственно перезапускать основной цикл. Однако, когда Symtab выполняет свой метод load, он добавляет свой собственный обработчик исключений:

 

% Symtab load {

    FILE * fp;

    void * in;

    void * cleanup;

    ...

    if (! (fp = fopen(fnm, "r")))

        return EOF;

 

    cleanup = new(Exception());

    if (catch(cleanup)) {

        fclose(fp);

        delete(cleanup);

        cause(1);

        assert(0);

    }

    while (in = retrieve(fp))

        ...

    fclose(fp);

    delete(cleanup);

    return result;

}

 

Мы видели в разделе 12.5, что есть проблема, если load() работает с выражением и если getsymbol() не может подключить имя к соответствующему символу в таблице:

 

else if (lex(result) != token)

    error("%s: need a %s, got a %s",

            buf, nameOf(class), nameOf(classOf(result)));

 

Всё, что теперь требуется сделать, это вызвать error() для распечатки сообщения. error() порождает исключение, которое отлавливается в данный момент через объект исключения cleanup в load(). В этом обработчике известно, что поток fp должен быть закрыт перед тем, как может быть завершена load(). Когда cleanup удалён и порождается другое исключение, управление наконец достигает обычного обработчика исключений, установленного с помощью retry в main(), где лишние узлы удаляются и перезапускается основной цикл.

Этот пример демонстрирует, что лучше разрабатывать cause() как функцию, которая лишь передаёт код исключения. error() можно вызывать из различных защищённых контекстов и она автоматически вернётся к надлежащему обработчику исключений. Удаляя соответствующий объект исключения и вызывая cause() снова мы можем вызвать срабатывание всех обработчиков вверх по цепочке.

Обработка исключений пахнет goto со всей его дурной славой. Следующий ужасный пример для создания вывода использует switch и два объекта исключения

 

$ except

caused -1

caused 1

caused 2

caused 3

caused 4

 

Вот этот код; дополнительные очки присуждаются, если вы проследите его с карандашом...

 

int main () {

    void * a = new(Exception()), * b = new(Exception());

 

    cause(-1); puts("caused -1");

 

    switch (catch(a)) {

    case 0:

        switch (catch(b)) {

        case 0:

            cause(1); assert(0);

        case 1:

            puts("caused 1");

            cause(2); assert(0);

        case 2:

            puts("caused 2");

            delete(b);

            cause(3); assert(0);

        default:

            assert(0);

        }

    case 3:

        puts("caused 3");

        delete(a);

        cause(4);

        break;

    default:

        assert(0);

    }

    puts("caused 4");

    return 0;

}

 

Этот код, конечно, ужасен и непонятен. Однако, если, чтобы создать набор операций, показанный в начале этой главы, используется обработка исключений

 

OOC_Strategy_1

 

мы все ещё поддерживаем для управляющих структур парадигму один вход, один выход, которая является основой структурного программирования. Класс Exception обеспечивает чистый, инкапсулированный механизм для вложения обработчиков исключений, и таким образом позволяет вложить одно защищённое опасное действие в другое.

 

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