4.7 Реализация подкласса — Circle |
Предыдущая Содержание Следующая |
Мы готовы написать полную реализацию окружностей, где можем выбрать любую технику из предыдущих разделов, которая больше всего нравится. Объектная ориентация предписывает, что нужен конструктор, возможно, деструктор, Circle_draw() и описание типа Circle, чтобы связать всё это вместе. Чтобы заняться нашими методами, подключаем Circle.h и добавляем следующие строки к переключателю в тестовой программе раздела 4.1:
case ’c’: p = new(Circle, 1, 2, 3); break;
Теперь наблюдаем такое поведение тестовой программы:
$ circles p c "." at 1,2 "." at 11,22 circle at 1,2 rad 3 circle at 11,22 rad 3
Конструктор окружности принимает три аргумента: сначала координаты точки окружности, а затем радиус. Инициализация компонента "точка" является работой конструктора точки. Он использует часть списка аргументов new(). Конструктору окружности остаётся со списком оставшихся аргументов, с помощью которых он инициализирует радиус. Конструктор подкласса должен сначала дать конструктору суперкласса сделать ту часть инициализации, которая превращает обычную память в суперкласс объекта. После того, как конструктор суперкласса выполнился, конструктор подкласса завершает инициализацию и превращает объект суперкласса в объект подкласса. Для окружностей это означает, что надо вызвать Point_ctor(). Подобно всем динамически скомпонованным методам, эта функция объявлена static и таким образом скрыта внутри Point.c. Тем не менее, мы всё ещё можем добраться до этой функции с помощью дескриптора типа Point, который доступен в Circle.c:
static void * Circle_ctor (void * _self, va_list * app) { struct Circle * self = ((const struct Class *) Point) -> ctor(_self, app);
self -> rad = va_arg(* app, int); return self; }
Теперь должно быть понятно, почему мы передаём адрес указателя списка аргументов app каждому конструктору, а не само значение va_list: new() вызывает конструктор подкласса, который вызывает конструктор своего суперкласса, и так далее. Конструктор самого базового класса является первым, кто на самом деле что-то сделает, и он первым забирает из левой части списка аргументов, переданного в new(). Остальные аргументы доступны следующему подклассу и так далее до последнего, пока крайние справа аргументы не будут использованы последний подклассом, то есть конструктором непосредственно вызванным new(). Разрушение лучше всего расположить в точном обратном порядке: delete() вызывает деструктор подкласса. Он должен разрушить свои собственные ресурсы, а затем вызвать деструктор своего непосредственного суперкласса, который сможет уничтожить следующий набор ресурсов и так далее. Конструирование происходит в порядке от суперкласса до подкласса, разрушение происходит в обратном порядке, подкласс перед суперклассом, компонент "окружность" перед компонентом "точка". Здесь, однако, ничего делать не надо. Перед этим мы работали над Circle_draw(). Мы используем видимые компоненты и код файла представления Point.r следующим образом:
struct Point { const void * class; int x, y; /* координаты */ };
#define x(p) (((const struct Point *)(p)) -> x) #define y(p) (((const struct Point *)(p)) -> y)
Теперь в Circle_draw() можно использовать макроопределения доступа:
static void Circle_draw (const void * _self) { const struct Circle * self = _self;
printf("circle at %d,%d rad %d\n", x(self), y(self), self -> rad); }
move() имеет статическую компоновку и наследуется от реализации точек. Мы решили реализовать окружности путём определения описания типа, который является единственной глобально видимой частью Circle.c:
static const struct Class _Circle = { sizeof(struct Circle), Circle_ctor, 0, Circle_draw };
const void * Circle = & _Circle;
Хотя всё выглядит так, как будто мы имеем жизнеспособную стратегию распределения текста программы, реализуя класс через интерфейс, представление и файл реализации, пример точек и окружностей не выявил одну проблему: если динамически скомпонованный метод, такой как Point_draw(), не будет перезаписан в подклассе, дескриптор типа подкласса должен указывать на функцию, реализованную в суперклассе. Однако, имя функции определяется там как static, так что селектор не может быть обманут. Мы увидим чистое решение этой проблемы в главе 6. В качестве временной меры, мы бы избегали использования static в этом случае, декларировали заголовок функции только в файле реализации подкласса, а также использовали имя этой функции для инициализации описания типа для подкласса.
|
Предыдущая Содержание Следующая |