8.4 Стандарт кодирования

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

Основная идея состоит в том, чтобы вызвать 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 *.

 

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