8.4 Стандарт кодирования |
Предыдущая Содержание Следующая |
Основная идея состоит в том, чтобы вызвать cast() так часто, как необходимо. Когда статически компонуемый метод разыменовывает объекты в своём собственном классе, он должен делать это с помощью cast():
void move (void * _self, int dx, int dy) { struct Point * self = cast(Point, _self);
self -> x += dx, self -> y += dy; }
Если такой метод получает объекты от другого класса, он может всё же вызвать cast(), чтобы удостовериться, что параметры являются тем, чем себя объявляют. Для обработки импорта списка параметров мы ввели в ooc запрос %casts:
% move { %casts self -> x += dx, self -> y += dy; }
%casts реализован в etc.rep с помощью отчёта casts; поэтому изменяя этот отчёт мы можем управлять импортом всех объектов. Первоначальная версия показана в разделе 7.4; вот как добавлен cast():
% casts // реализация запроса %casts `{() // импорт `{if `_ _ `t `const struct `cast * `name = ` \ cast( `cast , _ `name ); `n `}fi `}n `{if `linkage % // только для статической компоновки `%checks `}fi
Замена `_ определена как подчеркивание, если текущий параметр был определён с подчеркиванием в начале, то есть если он находится в текущем классе. Вместо простого присвоения мы вызываем cast(), чтобы выполнить проверку до разыменовывания указателя. Первый цикл в импорте заботится обо всём объекте в собственном классе метода. Другие объекты проверяются в таких проверках отчёта:
% checks // проверка всех других параметров объекта
`{() `{ifnot `cast ` `{ifnot `_ _ `t cast( `cast , `name ); `n `}fi `}fi `}n
Первоначально этот цикл генерировал для всех объектов assert(). Теперь мы можем ограничить наше внимание к тем объектам, которые не находятся в текущем классе. Для них мы генерируем вызов cast(), чтобы удостовериться, что они находятся в надлежащем классе. Отчёт по разному выполняет casts для методов со статической и динамической компоновкой. Статически компонуемые методы должны сделать свою собственную проверку. casts и checks генерируют локальные переменные для разыменовывания и операторов, чтобы проверять другие объекты, то есть %casts должна использоваться в конце списка локальных переменных, задекларированных вверху тела метода со статической компоновкой. Динамически компонуемые методы вызываются только через селекторы; поэтому работа по проверке может главным образом быть делегирована им. %casts всё же используется для разыменовывания объектов параметров в текущем классе, но только для инициализации локальных переменных:
Circle.dc
% Circle draw { %casts printf("circle at %d,%d rad %d\n", x(self), y(self), self -> rad); }
Point.c
void draw (const void * _self) { const struct Point * self = _self;
...
Circle.c
static void Circle_draw (const void * _self) { const struct Circle * self = cast(Circle, _self);
...
Надо быть осторожными: хотя селектор мог бы проверить, принадлежит ли объект текущему классу Point при вызове метода подкласса, подобного Circle_draw(), мы должны проверить, действительно ли объект представляет собой Circle. Поэтому мы позволяем селектору проверить объекты, которые не находятся в текущем классе, и мы позволяем динамически компонуемому методу проверить объекты в своём классе. casts просто опускают вызов checks для методов, которые вызываются через селектор. Теперь надо модифицировать селекторы. К счастью, все они генерируются отчётом init, но есть несколько особых случаев: селекторы с результатом void не выполняют return для результата фактического метода; селекторы со списком параметров аргументов должны передать указатель на фактический метод. init вызывает отчёт selectors в etc.rep, который в свою очередь делегирует фактическую работу отчёту selector и различным подотчётам. Вот типичный селектор:
int differ (const void * _self, const void * b) { int result; const struct Class * class = classOf(_self);
assert(class -> differ); cast(Object, b); result = class -> differ(_self, b); return result; }
Это сгенерировано отчётом selector в etc.rep (* Реальный отчёт немного сложнее для учёта методов со списком параметров аргументов.):
`%header { `n `%result `%classOf
`%ifmethod `%checks `%call `%return } `n `n
Отчёты result и return определяют и возвращают переменную result, если возвращаемый тип не void:
% result // если необходимо, определяем переменную result
`{ifnot `result void `t `result result; `}n % return // если необходимо, возвращаем переменную result
`{ifnot `result void `t return result; `}n
Отчёт ifmethod проверяет, существует ли желаемый метод:
% ifmethod // проверяем, существует ли метод
`t assert(class -> `method ); `n
Следует быть поаккуратнее с отчётом classOf: если селектор извлекает метод из Class, для создания подходящего описания класса можно положиться на classOf(), но для подклассов надо выполнять проверку:
`{if `meta `metaroot `t const struct `meta * class = classOf(_self); `n `} `{else `t const struct `meta * class = ` \ cast( `meta , classOf(_self)); `n `} `n
Селектор суперкласса аналогичен. Вот типичный пример:
int super_differ (const void * _class, const void * _self, const void * b) { const struct Class * superclass = super(_class);
cast(Object, b);
assert(superclass -> differ); return superclass -> differ(_self, b); }
Ещё раз, если мы работаем не с Class, то должны проверить результат super(). Вот отчёт из etc.rep:
% super-selector // селектор суперкласса
`%super-header { `n `{if `meta `metaroot // можно использовать super() `t const struct `meta * superclass = super(_class); `n `} `{else // надо вызвать cast `t const struct `meta * superclass = ` \ cast( `meta , super(_class)); `n `} `n `%checks
`t assert(superclass -> `method ); `n `t `{ifnot `result void return `} \ superclass -> `method \ ( `{() `_ `name `}, `{ifnot `,... ` , app `} ); `n } `n `n
Другие объекты проверяются с помощью checks, также как если бы селектор суперкласса был методом со статической компоновкой. Благодаря ooc и отчётам мы утвердили безопасный стандарт кодирования для всех методов, которые мы могли бы реализовать. Изменив всех селекторы и согласившись с использованием %casts во всех методах, мы учитываем все объекты, переданные в виде параметров: их указатели проверяются на импорт в вызываемом. Как следствие, результат метода можно не контролировать, потому что ожидается, что пользователь результата применит к нему cast(). Это отражено соглашением использования классов в возвращаемых типах наших методов. Например, в List.d:
Object @ addFirst (_self, const Object @ element);
Показанный в разделе 7.7 addFirst() возвращает void *. Однако, ooc генерирует:
struct Object * addFirst (void * _self, const void * element) { struct List * self = cast(List, _self);
cast(Object, element);
... return (void *) element; }
В прикладной программе struct Object представляет собой неполный тип. Таким образом, компилятор ANSI-C проверяет, что результат вызова addFirst() присвоен типу void * (чтобы быть проверенным позже, надо надеяться) или что он передаётся в метод, ожидающий void *, который в соответствии с нашими соглашениями проверит его с помощью cast(). В целом, аккуратно используя классы в типах возврата методов, мы можем использовать компилятор ANSI-C для проверки на маловероятные присвоения. Класс намного более строг, чем void *.
|
Предыдущая Содержание Следующая |