modern c++ design 第十一章

    技术2022-05-11  87

    本章介绍了经常遇到的双分派的一种泛型解决方案。 C++在语法上实现了单分派, 即虚函数, 通过动态机制选择相应的函数。 双分派是形如fun(Object1* a, Object2* b). 根据a和b的实际类型动态自动分派一个处理函数。 最容易想到的方案,蛮干法: 写一大堆重载函数. 不过这种方法会有很强的依赖性。 也提供了一种泛化蛮干法的实现: Executor是实现了各种具体实现的类, 其中实现了各种Fire函数. BaseLhs是左操作数的基类, TypesLhs是一个typelist, 包括了左操作数可能的类型集合. BaseRhs和TypesRhs类似,不过针对右操作数. symmetric是是否对称, 如果对称, 则需要实现的函数减半. ResultType是返回值类型,通常为void.     template     <         class Executor,         class BaseLhs,         class TypesLhs,         bool symmetric = true,         class BaseRhs = BaseLhs,         class TypesRhs = TypesLhs,         typename ResultType = void     >     class StaticDispatcher     {         template <class SomeLhs>         static ResultType DispatchRhs(SomeLhs& lhs, BaseRhs& rhs,             Executor exec, NullType)         { return exec.OnError(lhs, rhs); }                 template <class Head, class Tail, class SomeLhs>         static ResultType DispatchRhs(SomeLhs& lhs, BaseRhs& rhs,             Executor exec, Typelist<Head, Tail>)         {                        if (Head* p2 = dynamic_cast<Head*>(&rhs))             {                 Int2Type<(symmetric &&                           int(TL::IndexOf<TypesRhs, Head>::value) <                           int(TL::IndexOf<TypesLhs, SomeLhs>::value))> i2t;                 typedef Private::InvocationTraits<                         SomeLhs, Head, Executor, ResultType> CallTraits;                                     return CallTraits::DoDispatch(lhs, *p2, exec, i2t);             }             return DispatchRhs(lhs, rhs, exec, Tail());         }                 static ResultType DispatchLhs(BaseLhs& lhs, BaseRhs& rhs,             Executor exec, NullType)         { return exec.OnError(lhs, rhs); }                 template <class Head, class Tail>         static ResultType DispatchLhs(BaseLhs& lhs, BaseRhs& rhs,             Executor exec, Typelist<Head, Tail>)         {                        if (Head* p1 = dynamic_cast<Head*>(&lhs))             {                 return DispatchRhs(*p1, rhs, exec, TypesRhs());             }             return DispatchLhs(lhs, rhs, exec, Tail());         }     public:         static ResultType Go(BaseLhs& lhs, BaseRhs& rhs,             Executor exec)         { return DispatchLhs(lhs, rhs, exec, TypesLhs()); }     }; 实现方式还是模板元编程的方法, 利用静态递归实现. 如果对第三章内容有相当的理解,读懂这段也非常简单了. symmetric是使用bool的模板变量的分派实现的.  利用了一个辅助类InvocationTraits. 从辅助类的使用可以看到, 不同的职责都分派给了不同的类去实现, 耦合性非常低,利于重用.         template <class SomeLhs, class SomeRhs,             class Executor, typename ResultType>         struct InvocationTraits         {             static ResultType         DoDispatch(SomeLhs& lhs, SomeRhs& rhs,             Executor& exec, Int2Type<false>)             {                 return exec.Fire(lhs, rhs);             }             static ResultType         DoDispatch(SomeLhs& lhs, SomeRhs& rhs,             Executor& exec, Int2Type<true>)             {                 return exec.Fire(rhs, lhs);             }         }; 由于暴力法的依存性以及查找性能, 只有在类很少时使用.  必须考虑其他的实现 对数型分派.  依赖type_info的before函数进行排序, 维护一个执行期map, 为不同的组合注册callback, 根据2分查找寻找适合的callback, 可以实现对数性能. 即O(lgn).  此实现考虑到转型的问题, 函数调用进行了一次包装. 这一段读的不是很明白.  目的是得到具体型别不丢失, 需要实际使用才能更好理解吧. 利用前面提供的Functor可以分派到泛函数. 实现:    template     <         class BaseLhs,         class BaseRhs = BaseLhs,         typename ResultType = void,         typename CallbackType = ResultType (*)(BaseLhs&, BaseRhs&)     >     class BasicDispatcher     {         typedef std::pair<TypeInfo,TypeInfo> KeyType;         typedef CallbackType MappedType;         typedef AssocVector<KeyType, MappedType> MapType;         MapType callbackMap_;                 void DoAdd(TypeInfo lhs, TypeInfo rhs, CallbackType fun);         bool DoRemove(TypeInfo lhs, TypeInfo rhs);             public:         template <class SomeLhs, class SomeRhs>         void Add(CallbackType fun)         {             DoAdd(typeid(SomeLhs), typeid(SomeRhs), fun);         }                 template <class SomeLhs, class SomeRhs>         bool Remove()         {             return DoRemove(typeid(SomeLhs), typeid(SomeRhs));         }                 ResultType Go(BaseLhs& lhs, BaseRhs& rhs);     };     // Non-inline to reduce compile time overhead...     template <class BaseLhs, class BaseRhs,         typename ResultType, typename CallbackType>     void BasicDispatcher<BaseLhs,BaseRhs,ResultType,CallbackType>          ::DoAdd(TypeInfo lhs, TypeInfo rhs, CallbackType fun)     {         callbackMap_[KeyType(lhs, rhs)] = fun;     }             template <class BaseLhs, class BaseRhs,         typename ResultType, typename CallbackType>     bool BasicDispatcher<BaseLhs,BaseRhs,ResultType,CallbackType>          ::DoRemove(TypeInfo lhs, TypeInfo rhs)     {         return callbackMap_.erase(KeyType(lhs, rhs)) == 1;     }     template <class BaseLhs, class BaseRhs,         typename ResultType, typename CallbackType>     ResultType BasicDispatcher<BaseLhs,BaseRhs,ResultType,CallbackType>                ::Go(BaseLhs& lhs, BaseRhs& rhs)     {         typename MapType::key_type k(typeid(lhs),typeid(rhs));         typename MapType::iterator i = callbackMap_.find(k);         if (i == callbackMap_.end())         {                 throw std::runtime_error("Function not found");         }         return (i->second)(lhs, rhs);     } 由于多继承的问题, static转型在菱形继承的时候不能使用, 而Dynamic的转型性能消耗较大. 所以loki提供了一个转型策略. 最后, 还有一个BasicFastDispatcher. 是常数期的分派方法.  实际是使用了数组方法解决的问题, 代价是每个类都需要包括一个宏, 这个宏定义了那个类在数组中的下标. 对我来说, 了解了这些概念足以,  暂时的应用中,对数法或者暴力法可能更为实用一些. 高阶内容留待需求的提升吧. 不过对于思想的提升, 这些概念是意义重大的.  

    最新回复(0)