Modern C++ Design 笔记 第十一章 MultiMethods(2)

 紧接这上次没说完的来说。上回我们介绍了最基本的brute-force方案。那个安全的类型遍历匹配方案。说到在类数量比较多的时候不一定高效。很简单的可以知道原来的时间复杂度是O(n),n就是类的数量。所以估摸这转化成O(log(n))应该差不多了。所以这个第二方案就是用关联性的容器来替代依次遍历。我们把这个方案称之为The Logarithmic Double Dispatcher. 就像Scotter Mayer在《More Effective C++》中说的那样,把2个参数的类型信息作为key(这个东西是个pair),value就是具体要访问的函数指针(或者是个Functor)。

 

后面马上可以贴出来的就是一个标准的实现,注意这里用到了std::map这个关联性的容器和RTTI的typeid,在Loki里面用到的则是Loki自己的TypeInfo和AssocVector,用法和意义当然是非常类似的.

//////////////////////////////////////////////////////////////////////////////// // class template BasicDispatcher // Implements a logarithmic double dispatcher for functors (or functions) // Doesn't offer automated casts or symmetry //////////////////////////////////////////////////////////////////////////////// #include <map> template < class BaseLhs, class BaseRhs = BaseLhs, typename ResultType = void, typename CallbackType = ResultType (*)(BaseLhs&, BaseRhs&) > class BasicDispatcher { typedef std::pair<const char*,const char*> KeyType; typedef CallbackType MappedType; typedef std::map<KeyType, MappedType> MapType; MapType callbackMap_; void DoAdd(const char* lhs, const char* rhs, CallbackType fun); bool DoRemove(const char* lhs, const char* rhs); public: template <class SomeLhs, class SomeRhs> void Add(CallbackType fun) { DoAdd(typeid(SomeLhs).name(), typeid(SomeRhs).name(), fun); } template <class SomeLhs, class SomeRhs> bool Remove() { return DoRemove(typeid(SomeLhs).name(), typeid(SomeRhs).name()); } 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(const char* lhs, const char* rhs, CallbackType fun) { callbackMap_[KeyType(lhs, rhs)] = fun; } template <class BaseLhs, class BaseRhs, typename ResultType, typename CallbackType> bool BasicDispatcher<BaseLhs,BaseRhs,ResultType,CallbackType> ::DoRemove(const char* lhs, const char* 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).name(),typeid(rhs).name()); typename MapType::iterator i = callbackMap_.find(k); if (i == callbackMap_.end()) { throw std::runtime_error("Function not found"); } return (i->second)(lhs, rhs); }

这个实现比较简单,但是带来了一个非常头痛的问题,在这里CallbackType作为一个模板参数已经显示的放了出来,很显然为了覆盖所有的情况,这2个参数不能是具体的派生类,而只能是基类!BaseLhs和BaseRhs.问题就落在这些函数的实现上,比如说

typedef BasicDispatcher<Shape> Dispatcher; // Hatches the intersection between a rectangle and a polygon void HatchRectanglePoly(Shape& lhs, Shape& rhs) { Rectangle& rc = dynamic_cast<Rectangle&>(lhs); Poly& pl = dynamic_cast<Poly&>(rhs); ... use rc and pl ... } ... Dispatcher disp; disp.Add<Rectangle, Poly>(HatchRectanglePoly);

说实话,上面的代码在实现上是不太可以接收的.没有体现出模板的优势. 我们现在的考虑就是想把这些类型转换根据不同的类型由这个dispatcher自己做! 所以这里引入了一个叫FnDispatcher的类,其中的Add的函数的精髓就在这里得以体现. 首先确定的一点是CallbackType不再是一个固定的模板参数了.容器不需要知道这个CallbackType的具体类型是什么,只要另一个地方(一个内部的模板函数或者模板类存储这些信息就好了).

template <class ConcreteLhs, class ConcreteRhs, ResultType (*callback)(ConcreteLhs&, ConcreteRhs&)> void Add() { struct Local // see Chapter 2 { static ResultType Trampoline(BaseLhs& lhs, BaseRhs& rhs) { return callback( dynamic_cast<ConcreteLhs&>(lhs), dynamic_cast<ConcreteRhs&>(rhs)); } }; return backEnd_.Add<ConcreteLhs, ConcreteRhs>( &Local::Trampoline); }

在上面的这个实现里面,我们看到有个静态函数的定义, 在容器中存放的仅仅是这个静态函数的地址,而具体类型的定义在函数模板里面已经静态存在了.所以在调用静态函数的时候,可以把保留的这个类型提取出来做了dynamic_cast. 这就是所谓的"An idea that could help is using a trampoline function, also known as a thunk."

当然比这个更高级的实现就是用一个模板函数的内部类来保存这样的信息,在FunctorDispatcher中我们看到的这样的实现.

 

template <class SomeLhs, class SomeRhs, class Fun> void Add(const Fun& fun) { typedef Private::FunctorDispatcherHelper< BaseLhs, BaseRhs, SomeLhs, SomeRhs, ResultType, CastingPolicy<SomeLhs, BaseLhs>, CastingPolicy<SomeRhs, BaseRhs>, Fun, false> Adapter; backEnd_.template Add<SomeLhs, SomeRhs>(FunctorType(Adapter(fun))); }

这个FunctorDispatcherHelper的实现比原先的一个函数中的静态对象来的更为直接有序.同时有添加了Functor的支持,可以说是一个非常强大而优雅的实现.

纵观整个这个或者说这组对数时间的Dispatcher的实现,在效率上有了一定的提高,尤其在类数量比较大的情况下,同时和第一个的方案一样是非侵入式的,不会对原来的类结构造成影响.同时可以动态的增加减少dispatcher中的选项,应该说是个非常赞的实现!

最后再说一点,在最新的Loki库中我们发现了一个这样的语句

template <class SomeLhs, class SomeRhs> void Add(ResultType (*pFun)(BaseLhs&, BaseRhs&)) { return backEnd_.template Add<SomeLhs, SomeRhs>(pFun); }

需要说明的事情是backEnd_.template这里显示地告诉编译器Add后面的<>中间的是模板参数. 据说不然的话,编译器会把他解释成大与小与号而造成编译错误.但是在VS2005编译器下看,编译和运行结果都是一样.就是说不论加不加这个.template都是一样的结果. 同时花了点时间还调研了这里, 这里和这里.说的都比较含糊,而且在VS2005下运行的结果也和原文中的描述有出入.这样也好,这是一个很好需要精读《C++ Template》的理由:)

 

 

 

你可能感兴趣的:(Modern C++ Design 笔记 第十一章 MultiMethods(2))