Программирование >>  Многопоточная библиотека с принципом минимализма 

1 ... 87 88 89 [ 90 ] 91 92 93 ... 106


Надо же! Оказалось, достаточно нескольких строк. Очевидно, что подобный грубый подход вынуждает нас писать большое количество внешне тривиальных кодов. Такую паутину из условных операторов может сплести любой программист. Кроме того, это решение оказывается достаточно эффективным, если количество классов не слишком велико. Функция DoubleDispatch выполняет линейный поиск во множестве возможных типов. Поскольку поиск разворачивается (программа содержит последовательность операторов is-else, а не цикл), для небольших множеств алгоритм будет работать достаточно быстро.

Грубый подход порождает неприемлемый размер программы. При большом количестве классов программу становится невозможно поддерживать. Приведенный выше код работает лишь с тремя классами, но уже имеет значительный размер. С увеличением количества классов размер кода возрастает по экспоненциальному закону. Представьте себе эту программу, если иерархия состоит из 20 классов!

Вторая проблема заключается в том, что реализация функции DoubleDispatch должна иметь информацию о существовании всех классов, входящих в иерархию.

Третий недостаток функции DoubleDispatch заключается в том, что порядок следования операторов if имеет большое значение. Это очень сложная и опасная проблема. Вообразите, например, что из класса Rectangle выводится класс RoundedRec-tangle (прямоугольник с закругленными углами). Мы редактируем функцию Double-Dispatch, вставляя дополнительные операторы if в конец каждого оператора is-else, непосредственно перед вызовом функции Error. Вот и ошибка!

Если функции DoubleDispatch передается указатель на класс RoundedRectangle, оператор dynami c cast<Rectangle*> выполняется успешно. Поскольку эта проверка выполняется до проверки dynamic cast<RoundedRectangle*>, первая проверка проглотит и класс Rectangle, и класс RoundedRectangle. Вторая проверка становится излишней. Большинство компиляторов это совершенно не волнует.

Эти проверки следует немного изменить.

void DoubleDispatch(Shape& Ihs, Shape* rhs) {

if (typeid(lhs) == typeid(Rectangle)) {

Rectangle* pl = dynamic cast<Rectangle*>C&lhs);

else ...

Теперь проверка выполняется только для точного типа, а не для точного или производного. Сравнение typeid выдаст отрицательный результат, если указатель Ihs ссылается на объект класса RoundedRectangle, поэтому проверки будут продолжены. В итоге проверка typeidCRoundedRectangle) будет выполнена успешно.

Увы, исправляя один недостаток, мы создаем другой: функция DoubleDispatch становится слишком жесткой. Если в ней не предусмотрен какой-нибудь тип, естественно ожидать, что функция DoubleDispatch сработает на ближайшем к нему базовом типе. Именно на это мы обычно рассчитываем - по умолчанию производные объекты представляют собой базовые объекты, за исключением некоторых замещенных свойств. Проблема заключается в том, что реализация функции DoubleDispatch на основе оператора typeid этой возможностью не обладает. Отсюда следует, что в функции DoubleDispatch нужно по-прежнему использовать оператор dynamic cast и упорядочить проверки if, чтобы наиболее далекие потомки базового класса обрабатывались первыми.



Так возникают еще два недостатка грубого подхода к реализациям мультиметодов. Во-первых, зависимость функции DoubleDispatch от иерархии класса Shape увеличивается - функция должна знать не только о существовании классов, но и об их взаи-моотнощениях. Во-вторых, поддержка соответствующей систематизации динамических приведений типов связана с дополнительной нагрузкой.

11.4. Автоматизированный грубый подход

Поскольку в некоторых ситуациях быстродействие грубого подхода оказывается непревзойденным, имеет смысл реализовать такой диспетчер.

Напомним, что в библиотеке Loki определены списки типов - наборы структур и статических алгоритмов, позволяющих манипулировать коллекциями типов. Грубая реализация мультиметодов может использовать предоставленный пользователем список типов, в котором перечислены классы, входящие в иерархию (в нащем примере - классы Rectangle, Poly, Ellipse и др.). Затем рекурсивный щаблон может генерировать последовательность операторов if-els?.

В общем случае мы можем работать с разными коллекциями типов, поэтому список типов для левого операнда может отличаться от списка типов для правого операнда.

Попробуем набросать шаблонный класс staticDispatcher, выполняющий алгоритм вывода типов и вызывающий функцию из другого класса.

template <

class Executor,

class BaseLhs,

class TypeLhs,

class BaseRhs = BaseLhs,

class TypesRhs = TypesLhs,

typename ResultType = void

>

class StaticDispatcher {

typedef typename TypesLhs::Head Head; typedef typename TypesLhs::Tai1 Tail; public:

static ResultType GoCBaseLhs&, BaseRhs&, Executor exec)

if (Head* pi = dynamic cast<Head*>C&lhs)) {

return StaticDispatcher<Executor, BaseLhs, NullType, BaseRhs, TypesRhs>::DispatchRhsC *pl, rhs, exec);

else {

return StaticDispatcher<Executor, BaseLhs, Tail, BaseRhs, TypesRhs>::GoC Ihs, rhs, exec);



Класс staticDispatcher устроен довольно просто. Учитывая сложность задачи, которую он решает, размер его кода удивительно мал.

Класс StaticDispatcher имеет шесть шаблонных параметров. Класс Executor- эти тип объекта, выполняющего реальную работу, - в нашем примере он закрашивает область пересечения фигур. Егб внутреннее устройство мы рассмотрим немного позднее.

Классы BaseLhs и saseRhs представляют собой базовые типы аргументов левого и правого операндов соответственно. Классы TypeLhs и TypeRhs - это списки типов, состоящие из возможных производных классов для этих двух аргументов. По умолчанию классы BaseRhs и TypesRhs реализуют диспетчер для типов, входящих в одну и ту же иерархию, как в программе для рисования.

Класс ResultType - это тип результата двойной диспетчеризации. В общем случае вызываемая функция может возвращать произвольный тип. Класс StaticDispatcher поддерживает такую степень обобщенности и возвращает результат вызывающему модулю.

Функция StaticDispatcher: :Go выполняет динамическое приведение адреса Ihs к первому типу, указанному в списке TypesLhs. Если динамическое приведение оказалось невыполнимым, функция Go передает хвост списка TypesLhs своему рекурсивному вызову. (На самом деле это не настоящий рекурсивный вызов, поскольку каждый раз производится новая конкретизация класса StaticDispatcher.)

В итоге функция Go выполняет подходящий оператор if-else, применяющий оператор dynami c cast к каждому типу, указанному в списке. Обнаружив соответствие, функция Go вызывает функцию DispatchRhs. Это второй и последний этап вывода типа - обнаружение динамического типа объекта rhs.

template <...>

class StaticDispatcher

..г как и раньше ... template <c1ass SomeLhs>

static ResultType DispatchRhsCSomeLhs& Ihs, BaseRhs& rhs. Executor exec)

typedef typename TypesRhs::Head Head; typedef typename TypesRhs::Tai1 Tail;

if (Head* p2 = dynamic cast<Head*>C&rhs)) {

return exec.FireClhs, *p2) ;

else {

return StaticDispatcher<Executor, SomeLhs, NullType, BaseRhs, Tai1>::DispatchRhsC Ihs, rhs, exec);

Функция DispatchRhs вьтолняет для объекта rhs тот же алгоритм, который применялся функцией Go для объекта 1 hs. Кроме того, если динамическое приведение типа rhs выполнено успешно, функция DispatchRhs вызывает функцию Executor: :Fire, передавая ему два определенных типа. Как и прежде, код, генерируемый компилятором, представляет собой набор операторов is-e1se. Интересно, что компилятор генерирует для каждого типа, указанного в списке TypesLhs, отдельный набор операторов is-e1se. Действительно, класс StaticDispatcher порождает экспоненциальный объем кода, имея два



1 ... 87 88 89 [ 90 ] 91 92 93 ... 106

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика