Modern C++ Design 筆記 第十一章 MultiMethods(2)

來源:互聯網
上載者:User

 緊接這上次沒說完的來說。上回我們介紹了最基本的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,用法和意義當然是非常類似的.

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

這個實現比較簡單,但是帶來了一個非常頭痛的問題,在這裡CallbackType作為一個模板參數已經顯示的放了出來,很顯然為了覆蓋所有的情況,這2個參數不能是具體的衍生類別,而只能是基類!BaseLhs和BaseRhs.問題就落在這些函數的實現上,比如說

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

說實話,上面的代碼在實現上是不太可以接收的.沒有體現出模板的優勢. 我們現在的考慮就是想把這些類型轉換根據不同的類型由這個dispatcher自己做! 所以這裡引入了一個叫FnDispatcher的類,其中的Add的函數的精髓就在這裡得以體現. 首先確定的一點是CallbackType不再是一個固定的模板參數了.容器不需要知道這個CallbackType的具體類型是什麼,只要另一個地方(一個內部的模板函數或者模板類儲存這些資訊就好了).

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

在上面的這個實現裡面,我們看到有個靜態函數的定義, 在容器中存放的僅僅是這個靜態函數的地址,而具體類型的定義在函數模板裡面已經靜態存在了.所以在調用靜態函數的時候,可以把保留的這個類型提取出來做了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><br /> void Add(const Fun& fun)<br /> {<br /> typedef Private::FunctorDispatcherHelper<<br /> BaseLhs, BaseRhs,<br /> SomeLhs, SomeRhs,<br /> ResultType,<br /> CastingPolicy<SomeLhs, BaseLhs>,<br /> CastingPolicy<SomeRhs, BaseRhs>,<br /> Fun, false> Adapter;<br /> backEnd_.template Add<SomeLhs, SomeRhs>(FunctorType(Adapter(fun)));<br /> }

這個FunctorDispatcherHelper的實現比原先的一個函數中的靜態對象來的更為直接有序.同時有添加了Functor的支援,可以說是一個非常強大而優雅的實現.

縱觀整個這個或者說這組對數時間的Dispatcher的實現,在效率上有了一定的提高,尤其在類數量比較大的情況下,同時和第一個的方案一樣是非侵入式的,不會對原來的類結構造成影響.同時可以動態增加減少dispatcher中的選項,應該說是個非常贊的實現!

最後再說一點,在最新的Loki庫中我們發現了一個這樣的語句

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

需要說明的事情是backEnd_.template這裡顯示地告訴編譯器Add後面的<>中間的是模板參數. 據說不然的話,編譯器會把他解釋成大與小與號而造成編譯錯誤.但是在VS2005編譯器下看,編譯和運行結果都是一樣.就是說不論加不加這個.template都是一樣的結果. 同時花了點時間還調研了這裡, 這裡和這裡.說的都比較含糊,而且在VS2005下啟動並執行結果也和原文中的描述有出入.這樣也好,這是一個很好需要精讀《C++ Template》的理由:)

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.