8.1 Технические приёмы

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

Для обращение к всем объект используется void *. Хотя это упрощает написание кода, навлекается бедствие: манипулирование не-объектом или неправильным объектом в методе, или ещё хуже, выбор метода из описания класса, который его не содержит, вызовет значительные количеств проблем. Давайте проследим вызов динамически компонуемого метода. new() создаёт окружность и к нему применяется селектор draw():

 

void * p = new(Circle, 1, 2, 3);

    draw(p);

 

Селектор доверяет и разыменовывает результат classOf():

 

void draw (const void * _self) {

    const struct PointClass * class = classOf(_self);

 

    assert(class -> draw);

    class -> draw(_self);

}

 

Этот метод селектора доверяет и разыменовывает _self, который изначально является указателем, созданным new():

 

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);

}

 

classOf() также доверяет и разыменовывает указатель. В качестве небольшого утешения производится проверка результата на то, что указатель ненулевой:

 

const void * classOf (const void * _self) {

    const struct Object * self = _self;

 

    assert(self);

 

    assert(self -> class);

    return self -> class;

}

 

В целом каждое присвоение неопределённого значения void * указателю на некоторую структуру подозрительно с самого начала и законность его должна быть проверена. Мы спроектировали наши методы полиморфными, то есть компилятор ANSI-C не может выполнить эту проверку за нас. Мы должны изобрести динамическое средство для проверки типа, которое сильно ограничит ущерб, который может нанести оказавшийся не на своём месте объект или не-объект.

К счастью, наши значения void * знают, на что указывают: они указывают на объекты, которые все занаследованы от Object и поэтому содержат компонент .class, указывающий на их описание класса. Каждое описание класса уникально; таким образом значение указателя в .class может использоваться, чтобы определить, принадлежит ли объект конкретному классу:

 

int isA (const _self, const Class @ class);

int isOf (const _self, const Class @ class);

 

Это два новых статически компонуемых метода для Object и, следовательно, для любого объекта: isA() истина, если объект непосредственно принадлежит определённому классу; isOf() истина, если объект занаследован от указанного класса. Имеют силу следующие аксиомы:

 

isA(0, anyClass)     всегда ложь

isOf(0, anyClass)    всегда ложь

isOf(x, Object)      истина для всех объектов

 

Оказывается, что более полезен ещё один статический метод для Object:

 

void * cast (const Class @ class, const _self);

 

Если isOf(_self, class) истина, cast() возвращает свой аргумент _self, в противном случае cast() завершает работу вызывающего процесса.

cast() теперь собирается заменить assert() в большинстве мест контроля ошибок. Везде, где у нас нет уверенности, мы можем обернуть сомнительный указатель в cast(), чтобы ограничить ущерб, который могло бы нанести неожиданное значение:

 

cast(someClass, someObject);

 

Эта функция также используется для безопасного разыменовывания указателей во время импорта в метод или селектор:

 

struct Circle * self = cast(Circle, _self);

 

Обратите внимание, что параметры cast() имеют естественный порядок для операции по приведению типа: класс написан слева от объекта для приведения. isOf(), однако, берёт те же параметры в противоположном порядке, потому что в операции if мы спросили бы является ли объект "принадлежащим" указанному классу.

Хотя cast() принимает _self с определителем const, он возвращает значение const, чтобы избежать сообщений об ошибках при присвоении. Та же самая игра со словами происходит в стандарте ANSI-C: bsearch() возвращает результат void * для таблицы, переданной как const void *.

 

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