暴力活是程序员都不想做的,但有的时候你别无选择,但是这种情况下你并不是没有子弹了,暴力可做的事情,多数是类似的重复劳动,C++编译器可以帮我们做这些,只不过你要给编译器一个生成规则,模板是一个描述这种规则的工具。
我来展示一个极其野蛮的行为,有如下4个class:
class MyB;
class MyC;
class MyD;
为了对其双分配,我们需要如下方法:
void doSomething(MyA & ,MyB & );
void doSomething(MyA & ,MyC & );
void doSomething(MyA & ,MyD & );
void doSomething(MyB & ,MyB & );
void doSomething(MyB & ,MyC & );
void doSomething(MyB & ,MyD & );
void doSomething(MyC & ,MyC & );
void doSomething(MyC & ,MyD & );
void doSomething(MyD & ,MyD & );
其实2个或3个类就足以说明这个问题,所以我说这是一件极其野蛮的事情,因为使用了4个类。对于双分派的选择,情况是这样的:
... {
if (MyA* p = dynamic_cast<MyA*>(&lhs))
...{
if (MyA* q = dynamic_cast<MyA*>(&rhs))
...{
doSomething(*p,*q);
}
else if (MyB* q = dynamic_cast<MyB*>(&rhs))
...{
doSomething(*p,*q);
}
else if (MyC* q = dynamic_cast<MyC*>(&rhs))
...{
doSomething(*p,*q);
}
else if(MyD* q = dynamic_cast<MyD*>(&rhs))
...{
doSomething(*p,*q);
}
else
...{
doSomething(lhs,rhs);
}
}
else if(MyB* p = dynamic_cast<MyB*>(&lhs))
...{
if (MyA* q = dynamic_cast<MyA*>(&rhs))
...{
doSomething(*q,*p);
}
else if (MyB* q = dynamic_cast<MyB*>(&rhs))
...{
doSomething(*p,*q);
}
else if (MyC* q = dynamic_cast<MyC*>(&rhs))
...{
doSomething(*p,*q);
}
else if(MyD* q = dynamic_cast<MyD*>(&rhs))
...{
doSomething(*p,*q);
}
else
...{
doSomething(lhs,rhs);
}
}
else if(MyC* p = dynamic_cast<MyC*>(&lhs))
...{
if (MyA* q = dynamic_cast<MyA*>(&rhs))
...{
doSomething(*q,*p);
}
else if (MyB* q = dynamic_cast<MyB*>(&rhs))
...{
doSomething(*q,*p);
}
else if (MyC* q = dynamic_cast<MyC*>(&rhs))
...{
doSomething(*p,*q);
}
else if(MyD* q = dynamic_cast<MyD*>(&rhs))
...{
doSomething(*p,*q);
}
else
...{
doSomething(lhs,rhs);
}
}
else
...{
if (MyA* q = dynamic_cast<MyA*>(&rhs))
...{
doSomething(*q,*p);
}
else if (MyB* q = dynamic_cast<MyB*>(&rhs))
...{
doSomething(*q,*p);
}
else if (MyC* q = dynamic_cast<MyC*>(&rhs))
...{
doSomething(*q,*p);
}
else if(MyD* q = dynamic_cast<MyD*>(&rhs))
...{
doSomething(*p,*q);
}
else
...{
doSomething(lhs,rhs);
}
}
}
这真是太野蛮了,是的,我要让你看到的就是这样,实际上,碰到这样的问题,不管最后采用什么手段,最终的代码逻辑都是这样的,现在我们来考虑如何驱动编译器帮我们做这些无聊的事情。
我们必须有如下类型:
<
class Exec,
class LType,
class LTList,
class RType,
class RTList,
typename ResultType
>
这是双分派时的参与者们。
使用模板就是驱动编译器生成代码,所以驱动规则的时候请参考你写代码的逻辑,你考虑问题的逻辑被合理分析总结就可以被编译器合理使用,其实,这个过程中想到你是如何想到这个问题的解决方法的过程是非常重要的,语言如此的拗口,实际上,这个思想的行为和语言一样拗口。
按照我们写代码的逻辑,我们需要若干的最终执行者,它们被封装在Exec类里。我们需要lhs类型检索,然后rhs类型检索,如此我们有如下几个方法。
第一次分配:
<
class Exec,
class LType,
class LTList,
class RType,
class RTList,
typename ResultType
>
class StaticDispatcher
... {
typedef typename LTList::Head Head;
typedef typename LTList::Tail Tail;
static ResultType FirstDispach(LType& lhs,RType& rhs,Exec exc)
...{
if (Head* p1 = dynamic_cast<Head*>(&lhs))
...{
return
StaticDispatcher<Exec,LType,NullType,RType,RTList>::SecondDispach(*p1,rhs,exc);
}
else
...{
return
StaticDispatcher<Exec,LType,Tail,RType,RTList>::FirstDispach(lhs,rhs,exc);
}
}
static ResultType FirstDispach(NullType& lhs,RType& rhs,Exec exc)
...{
return exc.OnError(lhs,rhs);
}
} ;
如果找不到特定类型(TList走到了NullType),依据偏特化我们进行错误处理。
第二次分派:
<
class Exec,
class LType,
class LTList,
class RType,
class RTList,
typename ResultType
>
class StaticDispatcher
... {
typedef typename RTList::Head Head;
typedef typename RTList::Tail Tail;
template <typename SomeType>
static ResultType SecondDispach(SomeType& lhs,RType& rhs,Exec exc)
...{
if (Head* p1 = dynamic_cast<Head*>(&rhs))
...{
return exc.doSomething(lhs,*p1);
}
else
...{
return
StaticDispatcher<Exec,SomeType,NullType,RType,Tail>::SecondDispach(lhs,rhs,exc);
}
}
template <typename SomeType>
static ResultType SecondDispach(SomeType& lhs,NullType& rhs,Exec exc)
...{
return exc.OnError(lhs,rhs);
}
} ;
和第一次分派的想法一样。
我用非常朴素的想法描述了双分派的代码,Loki里有更好的东西。
这里有一个问题,就是子类可以合理向基类转换,这就要求我们的TList必须是有特定顺序的,幸好,我们有DerivedToFront工具。