The website "dmilvdv.narod.ru." is not registered with uCoz.
If you are absolutely sure your website must be here,
please contact our Support Team.
If you were searching for something on the Internet and ended up here, try again:

About uCoz web-service

Community

Legal information

7.1 Возвращаемся к Point

7.1 Возвращаемся к Point

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

Мы хотим спроектировать препроцессор ooc, который поможет поддерживать наши классы и стандарты кодирования. Лучший способ для разработки такого препроцессора это взять типичный, существующий пример класса и посмотреть как можно уменьшить затраты сил на реализацию, используя разумные предположения о том, что может сделать препроцессор. Одним словом, давайте некоторое время "поиграем" в препроцессор.

Point из главы 4 и раздела 6.10 является хорошим примером: это не базовый класс нашей системы, он требует нового метакласса и у него есть несколько типичных метода. С этого момента мы будем использовать курсив и упоминать его как Point, чтобы подчеркнуть, что он служит только в качестве модели того, что наш препроцессор должен обрабатывать.

Начнём с более или менее очевидного описания класса, который мы можем легко понять, и которое не слишком трудно для чтения препроцессором на основе awk:

 

% PointClass: Class Point: Object { // заголовок

    int x;                          // компоненты объекта

    int y;

%                                   // статически скомпонованные

    void move (_self, int dx, int dy);

%-                                  // динамически скомпонованные

    void draw (const _self);

%}

 

Жирный шрифт в этом описании класса указывает элементы, распознаваемые ooc; обычный шрифт используется для элементов, которые препроцессор читает здесь и воспроизводит в другом месте. Комментарии начинаются с // и продолжаются до конца строки; строки могут быть продолжены с помощью обратной косой черты.

Здесь мы опишем новый класс Point как подкласс Object. Эти объекты имеют новые компоненты х и у, оба типа int. Есть статически компонуемый метод move(), который может изменить свой объект, используя другие параметры. Мы также добавили новый динамически компонуемый метод draw(); Поэтому, мы должны начать с нового метакласса PointClass, расширяя мета суперкласс Class. Аргумент объекта draw() является const, то есть он не может быть изменён.

Если новых динамически связанных методов нет, описание ещё проще. В качестве типичного примера рассмотрим Circle:

 

% PointClass Circle: Point { // заголовок

    int rad;                 // компонент объекта

%}                           // статических методов нет

 

Эти простые, строчно-ориентированные описания, содержащие достаточно информации для того, чтобы полностью сгенерировать файлы интерфейса. Вот шаблон, чтобы предположить, как ooc создал бы Point.h:

 

#ifndef Point_h

#define Point_h

 

#include "Object.h"

 

extern const void * Point;

 

для всех методов в %

    void move (void * self, int dx, int dy);

 

если есть новый метакласс

    extern const void * PointClass;

 

    для всех методов в %-

        void draw (const void * self);

 

void initPoint (void);

 

#endif

 

Жирный шрифт отмечает части шаблона общие для всех файлов интерфейса. Обычный шрифт отмечает информацию, которую ooc должен прочитать в описании класса и вставить в файл интерфейса. Списки параметров немного обрабатываются: _self или const _self преобразуются в подходящие указатели; другие параметры могут быть скопированы напрямую.

Части шаблона используются многократно, например, для всех методов с определённой компоновкой или для всех параметров метода. Другие части шаблона зависят от условий таких, как новый определяемый метакласс. Это обозначено курсивом и отступами.

Описание класса также содержит достаточно информации для получения файла представления. Вот шаблон для генерации Point.r:

 

#ifndef Point_r

#define Point_r

 

#include "Object.r"

 

struct Point { const struct Object _;

    для всех компонентов

        int x;

        int y;

};

 

если есть новый метакласс

    struct PointClass { const struct Class _;

        для всех методов в %-

            void (* draw) (const void * self);

};

 

для всех методов в %-

    void super_draw (const void * class, const void * self);

 

#endif

 

Оригинальный файл можно найти в разделе 6.10. Он содержит определения для двух макросов доступа x() и y(). Чтобы ooc мог вставить их в файл представления, мы принимаем соглашение о том, что файл описания класса может содержать дополнительные строки в дополнение к самому описанию класса. Эти строки копируются в файл интерфейса или если им предшествует строки с %prot — в файл представления. prot относится к защищённой информации — такие строки доступны для реализаций класса и его подклассов, но не к приложению, использующему класс.

Описание класса содержит достаточно информации, поэтому ooc сможет также генерировать значительный объём файла реализации. Давайте в качестве примера посмотрим на различные части Point.c:

 

#include "Point.h"                       // подключение

#include "Point.r"

 

Во-первых, файл реализации подключает интерфейс и файлы представлений.

 

                                         // заголовок метода

void move (void * _self, int dx, int dy) {

    для всех параметров                  // импорт объектов

        если параметр это Point

            struct Point * self = _self;

 

    для всех параметров                  // проверка объектов

        если параметр это объект

            assert(_self);

 

    ...                                  // тело метода

 

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

 

                                         // заголовок метода

static void Point_draw (const void * _self) {

    для всех параметров                  // импорт объектов

        если параметр это Point

            const struct Point * self = _self;

 

    ...                                  // тело метода

 

Для динамически компонуемых методов мы также выполняем проверки, создаём заголовки и импортируем объекты. Шаблон может быть немного другим, чтобы учесть тот факт, что селектор уже должен был проверить, что объекты являются теми, кеми они претендуют быть.

Однако, есть несколько проблем. В качестве подкласса Object наш класс Point может перезаписать динамически компонуемый метод, подобный ctor(), который впервые появился в Object. Если ooc создаёт заголовки всех методов, следует прочитать все описания суперклассов спускаясь к корню дерева класса. От имени суперкласса Object в описании класса для Point мы должны быть в состоянии найти файл описания класса для суперкласса. Очевидное решение состоит в сохранении описания для Object в файле с соответствующим именем, например, Object.d.

 

static void * Point_ctor (void * _self, va_list * app) {

    ...

 

Ещё одна проблема связана с тем, что Point_ctor() вызывает селектор суперкласса и, следовательно, не надо импортировать параметры объектов, как делал Point_draw(). Вероятно, хорошая идея, если у нас будет способ каждый раз говорить ooc, хотим ли мы импортировать и проверять объекты.

 

если есть новый метакласс

    для всех методов в %-

        void draw (const void * _self) {    // селектор

            const struct PointClass * class = classOf(_self);

 

            assert(class -> draw);

            class -> draw(self);

        }

                                            // суперкласс селектора

        void super_draw (const void * class, const void * _self) {

            const struct PointClass * superclass = super(class);

 

            assert(_self && superclass -> draw);

            superclass -> draw(self);

        }

 

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

 

если есть новый метакласс

                                            // конструктор метакласса

static void * PointClass_ctor (void * _self, va_list * app) {

    struct PointClass * self =

                        super_ctor(PointClass, _self, app);

    typedef void (* voidf) ();

    voidf selector;

    va_list ap = * app;

 

    while ((selector = va_arg(ap, voidf))) {

        voidf method = va_arg(ap, voidf);

 

        для всех методов в %-

            if (selector == (voidf) draw) {

                * (voidf *) & self -> draw = method;

                continue;

            }

    }

    return self;

}

 

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

 

const void * Point;                        // описания классов

если есть новый метакласс

const void * PointClass;

 

void initPoint (void)                      // инициализация

{

    если есть новый метакласс

        if (! PointClass)

            PointClass = new(Class, "PointClass",

                            Class, sizeof(struct PointClass),

                            ctor, PointClass_ctor,

                            (void *) 0);

 

        if (! Point)

            Point = new(PointClass, "Point",

                        Object, sizeof(struct Point),

                        для всех перезаписываемых методов

                        ctor, Point_ctor,

                        draw, Point_draw,

                        (void *) 0);

}

 

Функция инициализации зависит от цикла по всем динамически компонуемым методам, перезаписываемым в данной реализации.

 

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