Анимация
JavaScript
|
Главная Библионтека ... как и раньше ... backEnd .Add<SomeLhs, SomeRhs>C FunctorTypeCnew AdapterCfun)); Обратите внимание на то, что по умолчанию стратегия конкретизируется классом DynamicCast. В заключение попробуем воспользоваться стратегаей приведения типов для достижения интересных результатов. Рассмотрим иерархию классов, изображенную на рис. 11.4. Одна ее часть не использует "бриллиантовое" наследование, поэтому для нее можно смело применять оператор stati c cast. Например, оператор stati c cast успешно преобразует тип Shape& в тип Triangle*. С другой сторога>1, оператор static cast нельзя применять для приведения типа shape* к типу Rectangle* и любому производному от него типу, здесь нужно использовать оператор dynami c cast. Shape "Т"
RoundedRectangle Рис. 11.4. Иерархия классов, содержащая "бриллиантовую" часть Допустим, что мы определяем свою собственную стратегию приведения типов для данной иерархии классов. Назовем ее Supercast. По умолчанию в ней можно применять оператор dynami c cast, специализируя эту стратегию для отдельных ситуаций. template <class то, class From> struct ShapeCaster static to* CastCFrom* obj) { return dynamic cast<To*>(obj); tempi ateo class ShapeCaster<triangle, Shape> { static Triangle* CastCshape* obj) { return static cast<Triangle*>Cobj); Теперь мы во всеоружии - когда можно, используется быстрое приведение типов, а в остальных случаях - безопасное. 11.10. Мультиметоды с постоянным временем выполнения Статические диспетчеры слишком офаничены, а динамические слишком медленны. Что делать, если нам нужен быстрый и масштабируемый диспетчер? В этом случае нужно переработать свои классы, открыв их для вмешательства двойного диспетчера. Эта возможность открывает новые перспективы. Поддержка приведения типов остается без изменения, однако средства для хранения и обнаружения обработчиков придется изменить (ведь они имеют логарифмическое время выполнения). Чтобы разработать более совершенный механизм диспетчеризации, вновь зададимся вопросом: что такое двойная диспетчеризация? Ее можно интерпретировать как поиск функции (или функтора) на плоскости. На одной из осей отложены типы левого оператора, а на другой - правого. Найдя пересечение двух типов, мы отышем нужную функцию. На рис. 11.5 показана двойная диспетчеризация для двух иерархий классов- Shape и DrawDevice. Обработчиками являются функции рисования, которые знают, как изобразить объект класса Shape с помошью объекта DrawingDevice. Окружность Эллипс Треугольник Прямоугольник Рис. 11.5. Диспетчеризация иерархий Shape и DrawingDevice Нетрудно догадаться, что для достижения постоянного времени поиска точки на плоскости следует использовать индексированный доступ к элементам двумерной матрицы. Идея возникает немедленно: каждый класс должен ассоциироваться с уникальным целым числом, представляюшим собой индекс в матрице диспетчера. Доступ к этому числу из любого класса должен осушествляться за постоянное время. Для этого можно применить виртуальную функцию. При двойной диспетчеризации вызова диспетчер извлекает два индекса из двух объектов, обрашается к обработчику, записанному в матрице, и активирует его. Это связано со следующими затратами: два виртуальных вызова, одна операция индексирования матрицы и вызов функции через указатель на нее. На это уходит постоянное время. На первый взгляд идея прекрасна, однако не все ее детали легко реализовать. Например, поддержка индексирования скорее всего окажется неудобной. Для каждого класса нужно задать уникальный идентификационный номер, надеясь, что комтшлятор сможет обнаружить возможные дубликаты. Отсчет этих номеров следует начинать с нуля и не делать пропусков, в противном случае в матрице возникнут пустые участки. Намного лучше поручить индексирование самому диспетчеру. Каждый класс должен хранить свою статическую целочисленную переменную. Ее исходное значение должно равняться -1, что означает "не присвоенный номер". Виртуальная функция должна возврашать ссылку на это целочисленное значение, позволяя диспетчеру изменять его в ходе выполнения программы. При добавлении в матрицу нового обработчика диспетчер должен проверить его идентификационный номер. Если он равен -1, ему следует присвоить следуюший доступный индекс матрицы. Ниже приведена основная часть реализации - макрос, который следует поместить в каждый класс, входяший в иерархию. #define implement indexable class(SomeClass) static int& GetclassindexStatic()\ {\ static int index = -1; \ return index; \ virtual int& GetClasslndex()\ {\ assert(typeid(*this) == typeid(SomeClass));\ return GetClasslndexStaticO;\ Этот макрос следует поместить в открытый раздел каждого класса, для которого предусматривается множественная диспетчеризация. Шаблонный класс BasicFastDispatcher предоставляет те же функциональные возможности, что и прежний класс BasicDispatcher, используя другие механизмы хранения и извлечения данных, template < class BaseLhs, class BaseRhs = BaseLhs, typename ResultType = void, typename CallbackType = ResultType (*)(BaseLhsA, BaseRhs&) > class BasicFastDispatcher { typedef std:vector<callbackType> Row; typedef std::vector<Row> Matrix; Matrix callbacks ; int columns ; public: BasicFastDispatcherO : columns (0) {} template <class SomeLhs, SomeRhs> void Add(callЬасктуре pFun) { int& idxLhs = SomeLhs::GetclasslndexStatic(); if (idxLhs < 0) Именно множественная, a не только двойная. Это решение легко обобщить на случай множественной диспетчеризации. 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 |