8.1 Технические приёмы |
Предыдущая Содержание Следующая |
Для обращение к всем объект используется 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 *.
|
Предыдущая Содержание Следующая |