7.1 Возвращаемся к Point |
Предыдущая Содержание Следующая |
Мы хотим спроектировать препроцессор 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); }
Функция инициализации зависит от цикла по всем динамически компонуемым методам, перезаписываемым в данной реализации.
|
Предыдущая Содержание Следующая |