Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 [ 85 ] 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105

Эта маленькая программа выводит на экран сообщение, означающее "все в порядке". vistt(Paragraph*)

Разумеется, этот искусственный пример не может продемонстрировать всю мощь разработанного нами кода. Однако, если вспомнить, с какими трудностями мы столкнулись в предыдущем разделе при реализации инспекторов "с нуля", становится ясно, что теперь в нащем распоряжении есть средство для корректного создания инспектируемых иерархий и их дальнейшего инспектирования.

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

• Вывести корень иерархии из класса Basevisitable<YourReturnType>.

• Добавить в каждый класс SomeClass, входящий в инспектируемую иерархию, макрос DEFlNE visiTABLE(). (Теперь иерархию можно инспектировать, однако никаких зависимостей от класса visitor больше нет!)

• Вывести каждый конкретный инспектирующий класс из класса Basevisitor. Кроме того, для каждого класса X, подлежащего инспектированию, нужно вывести класс Somevisitor из класса visitor<x, YourReturnType>. Обеспечить замещение функции-члена Visit в каждом инспектируемом классе.

Диаграмма зависимостей, возникающая в результате этих действий, очень проста. Определение класса Somevisitor зависит по имени от каждого инспектируемого класса. Реализации функции-члена visit полностью зависят от классов, которыми они манипулируют.

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

В особых случаях функцию Accept лучше реализовывать непосредственно, а не с помошью макроса DEFINE VISITABLE(). Допустим, что мы определяем класс Section, производный от класса DocElement и содержащий несколько объектов класса Paragraph. Мы бы хотели инспектировать все объекты класса Paragraph, содержащиеся в объекте класса Section. В этом случае мы могли бы реализовать функцию Accept вручную.

class Section : public DocElement

функция Accept реализуется непосредственно,

а не через макрос DEFINE VISITABLE()

virtual ReturnType Accept(Basevisitor* v) {

for (каждый параграф в данном разделе) {

current paragraph->Accept(v);

Очевидно, что с помощью шаблона visitor можно делать все, что угодно. Код, приведенный выше, освобождает программиста от тяжелой необходимости создавать все с нуля.

Мы закончили разработку ядра реализации шаблона visitor, содержащего практически все, что необходимо для инспектирования. Продолжение следует.



10.5. Назад - к "простому" шаблону Visitor

Реализация обобщенного щаблона Acyclic visitor, определенная в предыдущем разделе, вполне удовлетворительно работает во многих ситуациях. Однако, если нужно создать быстродействующее приложение, динамическое приведение типов, выполняемое в функции Accept, может повергнуть вас в уныние, а измерения скорости работы ващей профаммы - прямо в состояние депрессии. Почему это происходит? Когда вы применяете оператор dynami c cast к некоторому объекту, система поддержки выполнения профамм должна произвести несколько действий. Код механизма RTTI должен определить, допустимо ли данное преобразование, и в случае положительного ответа вычислить указатель на объект результирующего типа.

Попробуем разобраться, как этого можно достичь при создании компилятора. Можно присвоить каждому типу, который фигурирует в профамме, уникальный целочисленный идентификатор. Этот идентификатор оказывается полезен и при обработке исключительных ситуаций. Тогда в виртуальную таблицу каждого класса компилятор записывает указатель на таблицу идентификаторов всех его подтипов. Вместе с ними компилятор должен хранить смещения подобъектов относительно базового объекта. Этой информации достаточно для правильного выполнения динамического приведения типов. Когда выполняется оператор dynamic cast<T2*>(pl), а парамеф р1 представляет собой "указатель на объект типа т1", система поддержки выполнения профамм про-смафивает таблицу типов в поисках типа, соответствующего типу Т2. Если соответствие с типом т2 обнаружено, система поддержки выполнения профамм выполнит необходимые арифметические операции с указателем и вернет результат. В противном случае будет возвращен нулевой указатель. Детали, в частности множественное наследование, еще больще усложняют и замедляют динамическое приведение типов.

Сложность описанного выще рещения равна 0(л), где п - количество базовых классов данного класса. Иными словами, время, зафачиваемое на динамическое приведение типов, возрастает линейно по мере углубления и расширения иерархий наследования.

В качестве альтернативы можно использовать таблицы хэширования, позволяющие повысить бысфодействие программы за счет больших зафат памяти. Кроме того, можно применить большую мафицу для всего приложения. В этом случае время, затрачиваемое на динамическое приведение типов, постоянно, однако размер профаммы резко возрастает, особенно, если в ней много классов. (Можно было бы сжать матрицу - тогда жизнь создателей компиляторов языка С-Ы- стала бы намного легче.)

Итак, оператор dynami c cast значительно снижает производительность профаммы, причем предсказать величину этого снижения невозможно. В некоторых случаях это оказывается совершенно неприемлемым. Следовательно, мы должны расширить нашу реализацию шаблона visitor, чтобы адаптировать "циклический" шаблон visitor, предложенный фуппой GoF. Это позволит повысить бысфодействие нашего приложения, так как в "циклическом" шаблоне visitor не используется динамическое приведение типов, хотя поддерживать его фуднее.

Работа шаблона visitor, предложенного группой GoF, уже описывалась в начале главы. Ниже перечисляются основные различия между "обычным" шаблоном visitor и шаблоном Acyclic visitor, реализованным в библиотеке Loki.

• Класс Basevisitor больше не является фиктивным. В нем определяется одна чисто виртуальная функция-член visit для каждого инспектируемого типа (в предположении, что мы используем перефузку функций).



• функция Acceptimpl должна измениться. В идеале макрос DEFINE VISITABLE() остается неизменным.

Все это сводится к следующему. У нас есть набор типов, подлежащих инспектированию: например, DocElement, Paragraph и RasterBitmap. Как выразить этот набор типов и манипулировать с ним? Естественно в этот момент на ум приходят списки типов, описанные в главе 3.

Списки типов - это именно то, что нам нужно. Мы хотим передавать список типов шаблонному классу CyclicVisitor в качестве шаблонного параметра, как бы говоря: "Я хочу, чтобы данный класс CyclicVisitor мог инспектировать данные типы". Сделать это можно следующим элегантным способом.

неполное объявление, необходимое для списка типов class DocElement; class Paragraph; class RasterBitmap;

инспектирует классы DocElement, Paragraph и RasterBitmap

typedef CyclicVisitor

<

void, тип возвращаемого значения

TYPELIST 3(DocElement, Paragraph, RasterBitMap)

>

Myvisitor;

Класс CyclicVisitor зависит no имени от классов DocElement, Paragraph и RasterBitmap.

Посмотрим, какие дополнения нужно сделать в нашем коде. Используем процедуру, описанную в главе 9 при определении обобщенной реализации шаблона Abstract Factory.

Определение класса Private::visitorBinder<R> содержится в файле visitor.h template <typename R, class TList>

class CyclicVisitor : public GenmScatteredHierarchy<TList, Private::visitorBinder<R>::Result>

typedef R ReturnType; template <class visited> ReturnType visit(visited& host) {

visitor<visited>& subobj = *this; return subobj.visit(host);

Следует отметить, что класс CyclicVisitor использует класс visitor в качестве строительного блока. Аналогичный способ был продемонстрирован в главе 3, а похожий пример был рассмотрен в главе 9 По существу, класс CyclicVisitor наследует класс visitor<T> для каждого типа т, указанного в списке TList.

Если передать классу CyclicVisitor список типов, он завершит наследование от класса visitor, конкретизированного для каждого типа, указанного в данном списке, объявив таким образом по одной чисто виртуальной функции visit для каждого типа. Иными словами, с функциональной точки зрения он эквивалентен базовому классу visitor, как это предписывает щаблон visitor, предложенный группой GoF.



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 [ 85 ] 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105