本章介绍了经常遇到的双分派的一种泛型解决方案。 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. 是常数期的分派方法. 实际是使用了数组方法解决的问题, 代价是每个类都需要包括一个宏, 这个宏定义了那个类在数组中的下标.
对我来说, 了解了这些概念足以, 暂时的应用中,对数法或者暴力法可能更为实用一些. 高阶内容留待需求的提升吧. 不过对于思想的提升, 这些概念是意义重大的.
最容易想到的方案,蛮干法: 写一大堆重载函数. 不过这种方法会有很强的依赖性。 也提供了一种泛化蛮干法的实现: 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. 是常数期的分派方法. 实际是使用了数组方法解决的问题, 代价是每个类都需要包括一个宏, 这个宏定义了那个类在数组中的下标.
对我来说, 了解了这些概念足以, 暂时的应用中,对数法或者暴力法可能更为实用一些. 高阶内容留待需求的提升吧. 不过对于思想的提升, 这些概念是意义重大的.