12.2 Сохранение объектов — puto()

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

puto() является методом Object, который записывает представление объекта по указателю FILE и возвращает количество записанных байтов.

 

Object.d

 

% Class Object {

        ...

%-

    int puto (const _self, FILE * fp); // вывод на экран

 

Object.dc

 

% Object puto {

%casts

    class = classOf(self);

    return fprintf(fp, "%s at %p\n", class -> name, self);

}

 

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

Хотя каждый подкласс свободен реализовать свою собственную версию puto(), самое простое решение состоит в том, чтобы позволить puto() работать точно так же, как конструктору, то есть дойти по всей цепочке вызовов суперклассов обратно к Object_puto() и таким образом гарантировать, что вывод начинается с имени класса и подхватывает информацию об экземпляре из каждого вовлечённого класса. Таким образом, каждый метод puto должен волноваться лишь об информации, которая добавлена в его собственном классе, а не о всём содержании объекта. Рассмотрим Var и Symbol:

 

% Var puto {

    int result;

%casts

    result = super_puto(Var(), _self, fp);

    return result + fprintf(fp, "\tvalue %g\n", self -> value);

}

 

% Symbol puto {

    int result;

%casts

    result = super_puto(Symbol(), _self, fp);

    return result + fprintf(fp, "\tname %s\n\tlex %d\n",

                            self -> name, self -> lex);

}

 

Получается примерно такой вывод:

 

Var at 0x50ecb18          Object

    name x                Symbol

    lex 118

    value 1               Var

 

Заманчиво упростить этот код, чтобы избежать использования переменной с типом int:

 

% Var puto { // НЕПРАВИЛЬНО

%casts

    return super_puto(Var(), _self, fp)

        + fprintf(fp, "\tvalue %g\n", self -> value);

}

 

Однако, ANSI-C не гарантирует, что операнды оператора, подобного +, вычисляются слева направо, то есть мы могли бы получить выходные строки в перемешанном порядке.

Разработка вывода puto() проста для простых объектов: распечатываем каждый компонент в подходящем формате и используем puto(), чтобы позаботиться об указателях на другие объекты — по крайней мере до тех пор, пока мы уверены, что не столкнемся с циклом.

Контейнерный класс, то есть класс, который управляет другими объектами, обработать труднее. Вывод должен быть разработан так, чтобы он мог быть правильно восстановлен, особенно если должно быть записано и считано обратно неизвестное количество объектов; в отличие от save(), показанной в разделе 12.1, нельзя полагаться на конец файла, чтобы указать, что объектов больше нет.

В целом нам необходима префиксная нотация: или пишем количество объектов до последовательности фактических объектов, или предваряем каждый объект символом, например знаком "плюс", и используем, например, точку вместо знака "плюс", чтобы указать, что объектов больше нет. Для проверки следующего символа можно было бы использовать ungetc(getc(fp), fp), но если мы используем отсутствие определённого ведущего символа, чтобы завершить последовательность, мы фактически полагаемся на другие объекты, чтобы случайно не повредить нашу схему.

Fun в нашем калькуляторе представляет другой вид контейнерного класса: это символ, содержащий выражение, состоящее из символов Node. puto() выводит дерево выражения в заранее определённом порядке, узлы перед поддеревьями; если положение каждого узла известно, с помощью этой информации оно может быть легко восстановлено:

 

% Binary puto {

    int result;

%casts

    result = super_puto(Binary(), self, fp);

    result += puto(left(self), fp);

    return result + puto(right(self), fp);

}

 

Единственную загвоздку представляет функция, которая ссылается на другие функции. Если мы применяем puto() к ссылке вслепую и если не запрещаем рекурсивные функции, можно легко застрять. Чтобы отметить ссылки таблицы символов в дереве выражения, были введены Классы Ref и Val. Для ссылки на функцию мы просто записываем имя функции:

 

% Ref puto {

    int result;

%casts

    result = super_puto(Ref(), self, fp);

    result += putsymbol(left(self), fp);

    return result + puto(right(self), fp);

}

 

По причинам, которые станут понятны в следующем разделе, putsymbol() определена в parse.c:

 

int putsymbol (const void * sym, FILE * fp)

{

    return fprintf(fp, "\tname %s\n\tlex %d\n",

                                    name(sym), lex(sym));

}

 

Достаточно записать имя ссылки и значение маркера.

 

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