4.5 Статическая и динамическая компоновка

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

Подкласс наследует статически скомпонованные методы своего суперкласса и он может выбрать наследовать или перезаписать динамически скомпонованные методы. Рассмотрим декларации для move() и draw():

 

void move (void * point, int dx, int dy);

void draw (const void * self);

 

Невозможно обнаружить разницу компоновки двух деклараций, хотя реализация move() делает свою работу непосредственно, в то время как draw() лишь селекторная функция, которая прослеживает динамическую компоновку во время выполнения. Единственное отличие состоит в том, что мы декларируем статически скомпонованный метод, подобный move(), как часть интерфейса абстрактного типа данных в Point.h, и декларируем динамически скомпонованный метод, подобный draw(), вместе с интерфейсом управления памятью в new.h, потому что ранее мы решили реализовать селекторную функцию в new.c.

Статическая компоновка более эффективна, поскольку компилятор C может закодировать вызов подпрограммы с помощью прямого адреса, но функция, подобная move(), не может быть перезаписана для подкласса. Динамическая компоновка более гибка за счёт косвенного вызова — мы согласились с накладными расходами на вызов селекторной функции, подобной draw(), проверяя аргументы, выполняя поиск и вызывая соответствующий метод. Можно было бы отказаться от проверки и уменьшить накладные расходы с помощью макроса (* В ANSI-C макросы не раскрываются рекурсивно, поэтому макрос может скрыть функцию с тем же именем.), например

 

#define draw(self) \

    ((* (struct Class **) self) -> draw (self))

 

но макросы превносят проблемы, если их аргументы имеют побочные эффекты, и нет чистого способа для манипулирования со списком с переменным количеством аргументов с помощью макросов. Кроме того, макрос нуждается в декларации struct Class, которую мы до сих пор делали доступной только для реализаций классов, а не всему приложению.

К сожалению, выбор в значительной степени делается при разработке суперкласса. Хотя вызовы методов в функциях не меняются, уходит много времени на редактирование текста в возможно большом количестве классов, чтобы переключить декларацию функции со статической на динамическую компоновку и наоборот. Начиная с 7-й главы для упрощения кодирования мы будем использовать простой препроцессор, но даже в этом случае переключение вида компоновки подвержено ошибкам.

В случае возникновения сомнений, вероятно, лучше принять решение о динамической, а не статической компоновке, даже если он менее эффективен. Родовые функции могут служить полезной концептуальной абстракцией и они способствуют уменьшению количества имён функций, которые приходится помнить в ходе проекта. Если после реализации всех необходимых классов обнаружится, что динамически скомпонованный метод никогда не перезаписывается, намного меньше проблем заменить его селектор одной реализацией и даже выбросить его из struct Class, чем расширять описание типа и исправлять все инициализации.

 

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