作者:Kevin Lynx 需求: 开发一种组件,用以包装C函数、通常的函数对象、成员函数,使其对外保持一种一致的接口。我将最终的 组件称为functor,这里的functor与loki中的functor以及boost中的function功能一致,同STL中的functor 在概念层次上可以说也是一样的。那么,functor其实也可以进一步传进其他functor构成新的functor。 C++世界里还有一种组件,称做bind(er),例如STL中的binder1st、binder2nd,以及boost中的bind。所谓 的bind是将一些参数与函数之类的关联起来,当执行该bind创建的对象时,库会自动将之前bind的参数传 递给bind创建的对象。bind创建出来的对象在某种程度上来说也是一种functor。 实现: 包装C函数和函数对象的functor事实上是一致的,而实现包装成员函数的functor则需要多传入一个对象参数。 因此这里先讨论包装C函数和函数对象的functor。 包装C函数: 思考下各种不同的C函数的共同点和不同点,共同点就是这些函数都有一个返回值,参数个数可能相同,可能 不同,参数类型可能相同可能不同。考虑到模板对于类型的泛化特性,对于参数类型来说,可以轻松实现无 关性。而至于参数个数的泛化,则要复杂点。这里先考虑实现参数个数为1个的functor:
要使用这个类模板,可以这样:
 functor< int, int> cmd( func ); // int func( int )
 cmd( 1 );  这样,functor这个类模板就可以保存所以只有一个参数返回值任意的函数。但是这里首要的问题是,这个 类模板无法保存具有相同类型的函数对象,例如函数对象:
Func obj; 因为obj的类型事实上是Func,并不是一般的函数类型(例如 int (*)(int) )。那么,这里就需要 将functor::func_type这个typedef泛化。
包装函数对象: 要实现这个目的,其实并不那么容易。一种比较直接的方法是我们把functor::func_type通过模板参数显示地让用户配置, 例如:
那么,现在就可以这样使用functor:
 functor< int, int, int(*)( int)> cmd( func );  cmd( 1 ); // 测试函数对象
 Func obj;  functor< int, int, Func> cmd2( obj );  cmd2( 2 );  自动推导类型:
但是,这种显示指定functor保存的函数(函数对象)的类型显然是不方便的。我希望functor可以自动获取我们要 保存的东西(C函数,函数对象,为方便起见,以下全部简称为函数)的类型。而一个函数模板正可以做到这一点。 以下简写很多思考过程,直接给出一个解决方案:
代码多了一倍,还增加了多态机制,使用了动态内存分配(这总会为我们增加麻烦),所以这些,就是为了提供 给用户一个方便一致的接口。现在我们可以这样使用functor:
 functor< int, int> cmd1( func );  cmd1( 1 );   Func obj;  functor< int, int> cmd2( obj );  cmd2( 2 );   虽然目标实现了,可是看上去并不完美。碍眼的就是那个virtual,以及new/delete。不过因为这里离我的最终 目标还很远,所以姑且不管这些。接下来要实现的是让functor支持任意个参数(事实上任意个是不可能的)。
让更多的类型加入进来: 这里支持任意个参数似乎不现实,因为C++并不支持这样的语法形式:
 template <typename _R,  > class functor;  也就是说模板并不支持可变参数。(可变参数那是C里面的东西,C++本身就不鼓励)
这里,最简单的实现方法就是定义各种functor,支持0个参数的functor,支持一个参数的functor(我们以上实现的), 支持两个参数的functor,等等。相应的,我们给每一个functor命名为functor0,functor1,functor2,。。。 这确实是一种朴实的解决方法,但同时看上去也确实很不优雅。我们其实完全可以通过一种模板技术让functor1这种 丑陋的命名方式消失,这就是模板偏特化(partial specialization)。 Loki中的魔法: 首先我们要让functor这个顶层类可以看上去似乎支持可变长度的模板参数。这个可以通过loki的TypeList实现。但是 我们这里并不会用到特别复杂的TypeList技术。所谓TypeList,大致上核心在于以下类型:
然后我们可以以一种递归的方式去容纳任意长度的类型列表(所谓type list): type_list<int, type_list<char, float> > 在实际实现时,我们通常会为每一个type list添加一个在loki中叫null_type的类型,就像C字符串末尾的'\0'一样: type_list<int, type_list<char, null_type> > 而null_type很简单,就是一个没有任何东西的空类型:
struct null_type { };  为了更方便地产生type_list,我们按照loki中的做法,定义一系列的宏:
注:以上内容基本和<C++设计新思维>部分内容相同
讲述了以上基本内容(我希望你能理解),接下来我要阐述下我的目的。我会把新的functor定义成:
 template <typename _R, typename _ParamList> class functor;  如你所见,这和之前的functor本质上是一样的,我只不过改变了一个模板参数的名字(_ParamList)。现在当我们使用 functor的时候,会这样:
 functor< void, void>  functor< int, TYPE_LIST1( char )>  functor< void, TYPE_LIST2( char, float )>  我们回头看下之前创建的functor模块的三个类是如何相互关联的:functor提供给外部用户接口,handler保存函数、回调 函数,handler_base则主要是提供给functor一个可以保存的类型(所以functor里保存的是functor_base)以及声明各种接口。 为什么需要提供handler_base,而不直接保存handler?因为handler需要保存函数的类型_FuncType,而这个类型只能在functor构造 函数里被提取出来。局限于这个原因,我加入了handler_base,并不得不加入了virtual,而为了满足virtual的需要,我进一步 不得不将handler方在堆栈上。
现在,我要实现通过functor不同的模板参数(主要在于_ParamList),产生不同的handler_base。关键在于我要产生各种不同的 handler_base!现在我省略很多思考过程,直接给出一种架构:  template <typename _R, typename _ParamList> struct handler_base;   template <typename _R> struct handler_base<_R, void> : public handler_type_base<_R> {
virtual _R operator() ( void ) = 0;
};   template <typename _R, typename _P1> struct handler_base<_R, TYPE_LIST1( _P1 )> : public handler_type_base<_R> {
typedef _P1 param1_type;

virtual _R operator() ( _P1 ) = 0;
};  /// TODO:添加更多类型的偏特化版本  template <typename _R, typename _ParamList, typename _FuncType> class handler : public handler_base<_R, _ParamList> {
public:
typedef _FuncType func_type;

typedef handler_base<_R, _ParamList> base_type;
typedef typename base_type::param1_type param1_type;
/// TODO:更多的类型定义
public:
handler( const func_type &func ) :
_func( func )
{
}

_R operator() ()
{
return _func();
}

_R operator() ( param1_type p )
{
return _func( p );
}
/// 省略部分代码
/// functor
template <typename _R, typename _ParamList>
class functor
{
public:
typedef handler_base<_R, _ParamList> handler_type ;

typedef typename handler_type::param1_type param1_type;
typedef typename handler_type::param2_type param2_type;
typedef typename handler_type::param3_type param3_type;
/// TODO:更多类型
public:
template <typename _FuncType>
functor( _FuncType func ) :
_handler( new handler<_R, _ParamList, _FuncType>( func ) )
{
}
~functor()
{
delete _handler;
}

_R operator() ()
{
return (*_handler)();
}

_R operator() ( param1_type p )
{
return (*_handler)( p );
}
/// 省略部分代码

 现在,各种偏特化版本的handler_base,其实就相当于实现了各种参数个数的functor,也就是functor0,functor1等。但是 现在有个很直接的问题,例如当functor<void, int>定义了一个参数时,functor::handler_type里就没有param2_type之类的 类型定义,使用的偏特化版本handler_base也没有部分param之类的类型定义。这会引起编译出错。为了解决这个办法,我不得 不再引入一个用于类型定义的基类:
然后各种偏特化handler_base版本从handler_type_base继承:
解决了这个编译错误问题,整个functor就基本实现了。现在可以这样使用functor: 没有参数的函数:
 functor< void, void> cmd4( func3 );  cmd4();  两个参数的函数:
 functor< void, TYPE_LIST2( int, char)> cmd3( func2 );  cmd3( 3, 'a' );  我稍微提下编译器大致的处理方法:当functor<void, void> cmd4( func3 )时,functor::handler_type为handler_base<void, void>偏特 化版本。该版本定义了void operator()()函数。当cmd4()时,就会调用到handler::operator()()函数。该函数回调func3函数,完成调用。
完结,将成员函数包含进来: 关于包装成员函数,其实很简单,只是在调用时需要一个该类的对象而已。这里直接从handler_base派生:
在functor中加入另一个构造函数:
 template <typename _ObjType, typename _FuncType>  functor( _ObjType &obj, _FuncType func ) :  _handler( new mem_handler<_R, _ParamList, _FuncType, _ObjType>( obj, func ) ) {
} 一切都很完美。使用时:
 Test obj2; // Test是一个类
 functor< void, TYPE_LIST1( int)> cmd5( obj2, &Test::display );  cmd5( 1 ); 结束语: 虽然我们最终的目的实现了,但是这还是不够完美。我们还要处理functor的拷贝行为,因为functor天生就是被用来 四处拷贝的。一旦涉及到拷贝,我们就不得不小心翼翼地处理好functor中的那个被new出来的对象。作为一个C++程序员, 你应该时刻警惕放在heap上的东西,建立对heap上的警觉感是很重要的。这里我不得不承认在后期实现中,我直接搬了 loki中的很多方案。如果你不想让这个functor看上去那么优雅,那你完全可以写出functor0,functor1之类的东西。 参考资料: <C++ template>类模板的偏特化章节 <Modern C++ design>type list, functor章节 loki::functor源代码 boost:;function源代码 stl::bind1st源代码 stl::ptr_fun相关源代码
|