Анимация
JavaScript
|
Главная Библионтека Симметричная диспетчеризация в классе FnDispatcher реализована на уровне функций - для каждой зарегастрированной функции принимается отдельное решение. 11.8. Двойная диспетчеризация функторов Трюк с трамплинными функциями хорошо работает с нестатическими функциями. Безымянные пространства имен предоставляют ясный способ замены статических функций нестатическими, которые невидимы за пределами данной единицы компиляции. Однако иногда нужно, чтобы объект обратного вызова (шаблонный параметр CallbackType класса BasicDispatcher) представлял собой нечто большее, чем обычный указатель на функцию. Например, можно потребовать, чтобы каждый объект обратного вызова сохранял определенное состояние, в то время как функции могут сохранять только значения статических переменных. Следовательно, возникает необходимость регистрировать для двойной диспетчеризации функторы, а не функции. Функторы (глава 5) - это классы, которые перегружают оператор вызова функции С), имитируя синтаксис вызова простой функции. Кроме того, функторы могут использовать переменные-члены для хранения своего состояния и доступа к нему. К сожалению, трюк с трамплинными функциями не применим к функторам именно потому, что функторы сохраняют свое состояние, а простые функции - нет. (А где трамплинные функции могли бы хранить свое состояние?) Клиентский код может непосредственно использовать класс BasicDispatcher, конкретизированный соответствующим типом функтора. struct HatchFunctor { void operatorO(shapes, ShapeS) { typedef BasicDispatcher<shape, Shape, void, HatchFunctor> Hatchi ngDi spatcher; Функция HatchFunctor: :operatorO не является виртуальной, поскольку классу BasicDispatcher нужен функтор, имеющий семантику значений, а она плохо согласуется с динамическим полиморфизмом. Однако функция HatchFunctor: :operatorO может переадресовать вызов виртуальной функции. Гораздо хуже, что клиент теряет возможности автоматизации, предоставленные диспетчером, - приведение типов и поддержку симметрии. Похоже, что мы сделали шаг назад, но только если вы не читали главу 5 об обобщенных функторах. В этой главе определен класс Functor, способный содержать любые функторы и указатели на функции, а также объекты самого класса Functor. Можно даже определять специализированные объекты класса Functor, вьгеодя их из класса Functorlmpl. В классе Functor можно определить приведение типов. Поместив эти средства в библиотеку, можно легко реализовать симметричную диспетчеризацию. Определим класс FunctorDispatcher для любых объектов класса Functor. Этот диспетчер содержит класс BasicDispatcher, хранящий внутри себя объекты класса Functor. template <class BaseLhs, class BaseRhs = BaseLhs, typename ResultType = void> class FunctorDispatcher { typedef Functor<Resu1tType, TYPELiST 2(BaseLhs&, BaseRhs&)> FunctorType; typedef BasicDispatcher<BaseLhs, BaseRhs, ResultType, FunctorType> BackEndType; BackEndType backEnd ; public: Класс FunctorDispatcher использует конкретизацию класса BasicDispatcher в качестве выходного буфера. Класс BasicDispatcher хранит объекты типа FunctorType. Они представляют собой объекты класса Functor, получающие два параметра (классов BaseLhs и BaseRhs) и возвращающие объект класса ResultType. Функция-член FunctorDispatcher: :Add определяет специализированный класс Adapter, производный от класса Functorlmpl. Этот класс предназначен для приведения типов. Иными словами, он преобразовывает типы аргументов BaseLhs и BaseRhs в типы SomeLhs и SomeRhs (этот процесс называется адаптацией). template <c1ass BaseLhs, class BaseRhs = BaseLhs, ResultType = void> class FunctorDispatcher { ... как и раньше ... template <c1ass SomeLhs, class SomeRhs, class Fun> void Add(const Fun& fun); typedef Functorlmpl<Resu1tType, TYPELIST 2(BaseLhs&, BaseRhs&)> Functorlmplтуре; class Adapter : public FunctorlmplType { Fun fun ; virtual ResultType operatopO(BaseLhs& Ihs, BaseRhs& rhs) { return fun ( dynami c cast<SomeLhs&>(lhs), dynamic cast<SomeRhs&>(rhs)); virtual FunctorlmplType* Clone()const { return new Adapter; } public: Adapter(const Fun& fun): fun (fun) {} backEnd .Add<SomeLhs, SomeRhs>( FunctorType(FunctorimplType*)new Adapter(fun)); Класс Adapter делает то же, что и трамплинная функция. Поскольку функтор имеет состояние, класс Adapter содержит объекты класса Fun - для трамплинной функции это было невозможно. Функция-член Clone, имеющая очевидную семантику, нужна для класса Functor. Функция FunctorDispatcher: :Add имеет широкое применение. Ее можно использовать для регистрации не только указателей на функции, но и почти любого функтора, даже обобщенного. Для этого нужно лишь, чтобы тип Fun в функции Add допускал применение оператора () с аргументами типов SomeLhs и SomeRhs, а тип возвращаемого значения позволял преобразование в класс ResultType. В следующем примере показано, как регистрируются два разных функтора для объекта FunctorDispatcher. typedef FunctorDispatcher<shape> Dispatcher; struct HatchRectanglePoly void operator(Rectangles r, Poly& p) { struct HatchEllipseRectangle { void operator(Ellipse& r, Rectangle* p) { Dispatcher disp; disp.Add<Rectangle, Poly>(HatchRectanglePoly()); disp.Add<Ellipse, Rectangle>(HatchEllipseRectangle()); Два этих функтора никак не связаны между собой (хотя являются потомками одного и того же базового класса). Они лишь реализуют оператор () для типов, которые они должны обрабатывать. Реализация симметрии класса FunctorDispatcher аналогична реализации симметрии класса FnDispatcher. Функция FunctorDispatcher::Add определяет новый объект Re-verseAdapter, который выполняет приведение типов и меняет порядок вызовов. 11.9. Преобразование аргументов: static cast или dynannic cast? Ранее для приведения типов всегда применялся безопасный оператор dynami c cast. Однако эта безопасность достигалась за счет снижения эффективности профаммьг В момент регистрации мы уже знаем, что наша функция или функтор будут активированы для пары конкретных, точно известных типов. Таким образом, при обнаружении элемента, хранящегося в ассоциативном массиве, механизму двойной диспетчеризации известны фактические типы. Таким образом, излишне повторять проверку типов с помощью оператора dynamic cast, если простой оператор static cast позволяет достичь тех же результатов за меньшее время. Однако существуют две ситуации, в которых оператор stati c cast может оказаться неприемлемым, вынуждая прибегнуть к динамическому приведению типов с помощью оператора dynami c cast. Первая ситуация возникает при использовании виртуального наследования. Рассмотрим следующую иерархию классов. class Shape {...}; class Rectangle : virtual public Shape { ... }; class RoundedShape : virtual public Shape { ... }; class RoundedRectangle : public Rectangle, public RoundedShape { ... } Ha рис. 11.2 показаны отношения между классами, входящими в эту иерархию. 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 |