如果說前面我們已經把武功練到了第十重境界的話,最後一章意味著我們馬上要開啟任督二脈,一夜飛升了:)基於這樣的關鍵的時間點,我們願意非常謹慎的把這個問題分三次慢慢的講,講清楚。這第一次我們將說到這個MultiMethod話題的開始和第一個解決方案。
這個MultiMethod的問題源自於C++不支援多參數的函數多態,(其實原先我不覺得我們有這麼多這樣類型的需求,但是當看到不知道起什麼名字的時候我相信了)而且恰好這裡的需求和Alex的Sample驚人的類似。這裡我們不再對問題本身做過多的描述,而是直奔主題的尋求第一個最直觀的解決方案。
這個方案被命名為The Brute-Force,其實就是Dynamic_cast的反覆使用從而確定具體的類型是什麼,到確定類型以後,直接調用對應類型的子函數,下面我們展示一下最原始的實現代碼:
// various intersection algorithms<br />void DoHatchArea1(Rectangle&, Rectangle&);<br />void DoHatchArea2(Rectangle&, Ellipse&);<br />void DoHatchArea3(Rectangle&, Poly&);<br />void DoHatchArea4(Ellipse&, Poly&);<br />void DoHatchArea5(Ellipse&, Ellipse&);<br />void DoHatchArea6(Poly&, Poly&);<br />void DoubleDispatch(Shape& lhs, Shape& rhs)<br />{<br />if (Rectangle* p1 = dynamic_cast<Rectangle*>(&lhs))<br />{<br />if (Rectangle* p2 = dynamic_cast<Rectangle*>(&rhs))<br />DoHatchArea1(*p1, *p2);<br />else if (Ellipse p2 = dynamic_cast<Ellipse*>(&rhs))<br />231<br />DoHatchArea2(*p1, *p2);<br />else if (Poly p2 = dynamic_cast<Poly*>(&rhs))<br />DoHatchArea3(*p1, *p2);<br />else<br />Error("Undefined Intersection");<br />}<br />else if (Ellipse* p1 = dynamic_cast<Ellipse*>(&lhs))<br />{<br />if (Rectangle* p2 = dynamic_cast<Rectangle*>(&rhs))<br />DoHatchArea2(*p2, *p1);<br />else if (Ellipse* p2 = dynamic_cast<Ellipse*>(&rhs))<br />DoHatchArea5(*p1, *p2);<br />else if (Poly* p2 = dynamic_cast<Poly*>(&rhs))<br />DoHatchArea4(*p1, *p2);<br />else<br />Error("Undefined Intersection");<br />}<br />else if (Poly* p1 = dynamic_cast<Poly*>(&lhs))<br />{<br />if (Rectangle* p2 = dynamic_cast<Rectangle*>(&rhs))<br />DoHatchArea2(*p2, *p1);<br />else if (Ellipse* p2 = dynamic_cast<Ellipse*>(&rhs))<br />DoHatchArea4(*p2, *p1);<br />else if (Poly* p2 = dynamic_cast<Poly*>(&rhs))<br />DoHatchArea6(*p1, *p2);<br />else<br />Error("Undefined Intersection");<br />}<br />else<br />{<br />Error("Undefined Intersection");<br />}<br />}
看到這樣的代碼很多人的第一反應就是disgusting,這樣的代碼對於任何一個需要維護的程式員來說都是噩夢。當然這個時代需要大師幫主我們把這些東西掩蓋起來,提供給我們更為優雅的實現。如果我們還記得遞迴之美的TypeList的話,這一切都講變得豁然開朗。如果編譯器可以幫我們把這些IF-ELSE隱藏在TypeList的遞迴裡面可能這樣的方案在某種程度上仍然是個不錯的主意,讓這樣設計從模板參數開始,我們需要的參數大概是這樣的:
template<br /> <<br /> class Executor,<br /> class BaseLhs,<br /> class TypesLhs,<br /> class BaseRhs = BaseLhs,<br /> class TypesRhs = TypesLhs,<br /> typename ResultType = void<br /> ><br /> class StaticDispatcher;
這裡包括了executor(就是那個擁有一組類似介面的類),baseLhs就是第一參數的基類,TypesLhs就是第一參數的list,相同的後面有第二參數的基類的list,最有一個就是ResultType做傳回值。一樣的,我們還是做匹配,比如對於第一個參數我們有這樣的搜尋方式:
template <class Head, class Tail><br /> static ResultType DispatchLhs(BaseLhs& lhs, BaseRhs& rhs,<br /> Executor exec, Typelist<Head, Tail>)<br /> {<br /> if (Head* p1 = dynamic_cast<Head*>(&lhs))<br /> {<br /> return DispatchRhs(*p1, rhs, exec, TypesRhs());<br /> }<br /> return DispatchLhs(lhs, rhs, exec, Tail());<br /> }
如果是head*,那麼就處理第二參數,如果不是,就從這裡lhs裡面繼續遍曆。可以想見的是對於第二參數rhs,我們採用了相同的方式,只是優點在於,代碼優雅了不少。對任何一個維護著來說,也不需要在多加那些新加入類的處理。緊接著這裡,我們貼出整個一個完整的StaticDispatcher的實現:
////////////////////////////////////////////////////////////////////////////////<br />// class template InvocationTraits (helper)<br />// Helps implementing optional symmetry<br />////////////////////////////////////////////////////////////////////////////////<br /> namespace Private<br /> {<br /> template <class SomeLhs, class SomeRhs,<br /> class Executor, typename ResultType><br /> struct InvocationTraits<br /> {<br /> static ResultType<br /> DoDispatch(SomeLhs& lhs, SomeRhs& rhs,<br /> Executor& exec, Int2Type<false>)<br /> {<br /> return exec.Fire(lhs, rhs);<br /> }<br /> static ResultType<br /> DoDispatch(SomeLhs& lhs, SomeRhs& rhs,<br /> Executor& exec, Int2Type<true>)<br /> {<br /> return exec.Fire(rhs, lhs);<br /> }<br /> };<br /> }<br />////////////////////////////////////////////////////////////////////////////////<br />// class template StaticDispatcher<br />// Implements an automatic static double dispatcher based on two typelists<br />////////////////////////////////////////////////////////////////////////////////<br /> template<br /> <<br /> class Executor,<br /> class BaseLhs,<br /> class TypesLhs,<br /> bool symmetric = true,<br /> class BaseRhs = BaseLhs,<br /> class TypesRhs = TypesLhs,<br /> typename ResultType = void<br /> ><br /> class StaticDispatcher<br /> {<br /> template <class SomeLhs><br /> static ResultType DispatchRhs(SomeLhs& lhs, BaseRhs& rhs,<br /> Executor exec, NullType)<br /> { return exec.OnError(lhs, rhs); }</p><p> template <class Head, class Tail, class SomeLhs><br /> static ResultType DispatchRhs(SomeLhs& lhs, BaseRhs& rhs,<br /> Executor exec, Typelist<Head, Tail>)<br /> {<br /> if (Head* p2 = dynamic_cast<Head*>(&rhs))<br /> {<br /> Int2Type<(symmetric &&<br /> int(TL::IndexOf<TypesRhs, Head>::value) <<br /> int(TL::IndexOf<TypesLhs, SomeLhs>::value))> i2t;<br /> typedef Private::InvocationTraits<<br /> SomeLhs, Head, Executor, ResultType> CallTraits;</p><p> return CallTraits::DoDispatch(lhs, *p2, exec, i2t);<br /> }<br /> return DispatchRhs(lhs, rhs, exec, Tail());<br /> }</p><p> static ResultType DispatchLhs(BaseLhs& lhs, BaseRhs& rhs,<br /> Executor exec, NullType)<br /> { return exec.OnError(lhs, rhs); }</p><p> template <class Head, class Tail><br /> static ResultType DispatchLhs(BaseLhs& lhs, BaseRhs& rhs,<br /> Executor exec, Typelist<Head, Tail>)<br /> {<br /> if (Head* p1 = dynamic_cast<Head*>(&lhs))<br /> {<br /> return DispatchRhs(*p1, rhs, exec, TypesRhs());<br /> }<br /> return DispatchLhs(lhs, rhs, exec, Tail());<br /> }<br /> public:<br /> static ResultType Go(BaseLhs& lhs, BaseRhs& rhs,<br /> Executor exec)<br /> { return DispatchLhs(lhs, rhs, exec, TypesLhs()); }<br /> };
看到了這個完整的實現,我們需要補充說幾點:
1. 遞迴總是要收斂的,所以不管DispatchRhs還是DispatchLhs都會對NullType有一個偏特化的的OnError處理。這要求我們在這個Executor裡面有一個onError的實現。
2. 我們注意到這裡還有一個bool symmetric = true,的申明。從名字我們可以知道這是一個關於對稱性的開關,比較自然的。如果有兩個參數Rectangle&和Poly&,不管先後我都最好可以調用func(Rectangle&, Poly&)或者func(Poly&,Rectangle&)。所以這裡的Traits幫了我們的忙,注意這裡的IndexOf依賴於lhs和rhs的順序問題。還有這裡在搜尋lhs和rhs的時候其實都是沒有確定第一參數或者第二參數的。這裡的DispatchLhs或者DispatchRhs只是確定這些rhs和lhs在baselist裡面是存在的!所以這樣對稱性的討論感覺還是在兩個list的元素相同的情況下討論比較有意義。
3. 還有一個問題就是TypeList裡面的順利問題,這個問題有點想catch exception的順序問題,如果基類在前,那麼那些衍生類別就沒有機會被訪問到了。TYPELIST_4(Rectangle, Ellipse, Poly, RoundedRectangle)如果是這樣的一個申明,每次的RoundedRectangle&的類型在Rectangle&做dynamic_cast都成功的阻截了。所以你應該或者在申明的時候謹慎的手動組織好繼承關係,或者用第三章的DeriveToFront讓編譯器幫你組織。
總的來看,其實這樣優雅的實現還沒有改變想法本身,依然是匹配。根據變數的實際類型來匹配對應的函數,應該說在類結構比較簡單的時候比較好用,也比較迅速。但是當類型比較複雜的時候,就不是那麼實際。