loop_in_codes

低调做技术__欢迎移步我的独立博客 codemaro.com 微博 kevinlynx

实现functor - 增强型的函数指针

作者: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:

template <typename _R, typename _P1>
class functor
{
public:
 typedef _R (
*func_type)( _P1 );
public:
 
explicit functor( const func_type &func ) :
  _func( func )
 
{
 }

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


private:
 func_type _func;
}
;


要使用这个类模板,可以这样:

functor<intint> cmd( func ); // int func( int )
cmd( 1 );


这样,functor这个类模板就可以保存所以只有一个参数返回值任意的函数。但是这里首要的问题是,这个
类模板无法保存具有相同类型的函数对象,例如函数对象:

struct Func
{
    
int operator() ( int i )
    
{
        
return i;
    }

}
;


Func obj; 因为obj的类型事实上是Func,并不是一般的函数类型(例如 int (*)(int) )。那么,这里就需要
将functor::func_type这个typedef泛化。

包装函数对象

要实现这个目的,其实并不那么容易。一种比较直接的方法是我们把functor::func_type通过模板参数显示地让用户配置,
例如:

template <typename _R, typename _P1, typename _FuncType>
class functor
{
public:
 typedef _FuncType func_type;
 
//以下内容相同


那么,现在就可以这样使用functor:

functor<intintint(*)(int)> cmd( func );
cmd( 
1 );
// 测试函数对象
Func obj;
functor
<intint, Func> cmd2( obj );
cmd2( 
2 );


自动推导类型:

但是,这种显示指定functor保存的函数(函数对象)的类型显然是不方便的。我希望functor可以自动获取我们要
保存的东西(C函数,函数对象,为方便起见,以下全部简称为函数)的类型。而一个函数模板正可以做到这一点。
以下简写很多思考过程,直接给出一个解决方案:

template <typename _R, typename _P1>
struct handler_base
{
 
virtual _R operator() ( _P1 ) = 0;
}
;

template 
<typename _R, typename _P1, typename _FuncType>
class handler : public handler_base<_R, _P1>
{
public:
 typedef _FuncType func_type;
public:
 handler( 
const func_type &func ) :
   _func( func )
 
{
 }


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


public:
 func_type _func;
}
;

template 
<typename _R, typename _P1>
class functor
{
public:
 typedef handler_base
<_R, _P1> handler_type ;
public:
 template 
<typename _FuncType>
 functor( _FuncType func ) :
  _handler( 
new handler<_R, _P1, _FuncType>( func ) )
 
{
 }

 
 
~functor()
 
{
  delete _handler;
 }


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


private:
 handler_type 
*_handler;
}
;


代码多了一倍,还增加了多态机制,使用了动态内存分配(这总会为我们增加麻烦),所以这些,就是为了提供
给用户一个方便一致的接口。现在我们可以这样使用functor:

functor<intint> cmd1( func );
cmd1( 
1 );

Func obj;
functor
<intint> 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,大致上核心在于以下类型:

template <typename _T, typename _U>
struct type_list
{
 typedef _T head_type;
 typedef _U tail_type;
}
;


然后我们可以以一种递归的方式去容纳任意长度的类型列表(所谓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中的做法,定义一系列的宏:

#define TYPE_LIST1( T1 ) type_list<T1, null_type>
#define TYPE_LIST2( T1, T2 ) type_list<T1, TYPE_LIST1( T2 )>
#define TYPE_LIST3( T1, T2, T3 ) type_list<T1, TYPE_LIST2( T2, T3 )>
/// etc


注:以上内容基本和<C++设计新思维>部分内容相同

讲述了以上基本内容(我希望你能理解),接下来我要阐述下我的目的。我会把新的functor定义成:

template <typename _R, typename _ParamList>
class functor;


如你所见,这和之前的functor本质上是一样的,我只不过改变了一个模板参数的名字(_ParamList)。现在当我们使用
functor的时候,会这样:

functor<voidvoid>
functor
<int, TYPE_LIST1( char )>
functor
<void, TYPE_LIST2( charfloat )>


我们回头看下之前创建的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之类的类型定义。这会引起编译出错。为了解决这个办法,我不得
不再引入一个用于类型定义的基类:

template <typename _R>
struct handler_type_base
{
 typedef _R result_type;
 typedef null_type param1_type;
 typedef null_type param2_type;
 typedef null_type param3_type;
 
/// TODO:添加更多类型定义
}
;


然后各种偏特化handler_base版本从handler_type_base继承:

template <typename _R, typename _P1, typename _P2>
struct handler_base<_R, TYPE_LIST2(_P1, _P2 )> : public handler_type_base<_R>
{
 typedef _P1 param1_type;
 typedef _P2 param2_type;

 
virtual _R operator() ( _P1, _P2 ) = 0;
}
;


解决了这个编译错误问题,整个functor就基本实现了。现在可以这样使用functor:
没有参数的函数: 

functor<voidvoid> cmd4( func3 );
cmd4();


两个参数的函数:

functor<void, TYPE_LIST2( intchar)> 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派生:

 template <typename _R, typename _ParamList, typename _FuncType, typename _ObjType>
class mem_handler : public handler_base<_R, _ParamList>
{
public:
 typedef _FuncType func_type;
 typedef _ObjType obj_type;

 typedef handler_base
<_R, _ParamList> base_type;
 typedef typename base_type::param1_type param1_type;
 typedef typename base_type::param2_type param2_type;
 typedef typename base_type::param3_type param3_type;

public:
 mem_handler( obj_type 
&obj, const func_type &func ) :
  _obj( obj ), _func( func )
 
{
 }


 _R 
operator() ()
 
{
  
return (_obj.*_func)();
 }


 _R 
operator() ( param1_type p )
 
{
  
return (_obj.*_func)( p );
 }


 _R 
operator() ( param1_type p1, param2_type p2 )
 
{
  
return (_obj.*_func)( p1, p2 );
 }


private:
 obj_type 
&_obj;
 func_type _func;
}
;


在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相关源代码

 

 

posted on 2008-03-17 11:13 Kevin Lynx 阅读(7518) 评论(15)  编辑 收藏 引用 所属分类: c/c++

评论

# re: 实现functor - 增强型的函数指针 2008-03-17 16:43 梦在天涯

functor 确实是比STL中的一大堆好用多了!统一!  回复  更多评论   

# re: 实现functor - 增强型的函数指针 2008-03-17 16:57 魔域私服

http://www.zhaomysf.com.cn  回复  更多评论   

# re: 实现functor - 增强型的函数指针 2009-07-04 13:47 deadlm

这叫什么东西啊:functor<void, TYPE_LIST2( int, char)>
实现的非常差劲
就这样还敢说优雅
看到定义个变量跟水蛇一样长的东西就烦,一个词评价就是丑陋
等做到functor<void,int,char>再来说优雅,这是最基本的实现要求
  回复  更多评论   

# re: 实现functor - 增强型的函数指针 2009-07-04 17:56 Kevin Lynx

@deadlm
并不见得functor<void, int, char>就比function< void, TYPE_LIST2( int, char )>好。
functor<void, int, char>是需要诸如:
template <typename R, typename P1>
class lua_binder<R ( P1 )>
的语法支持。而并不见得所有的编译器都支持。另外,我没有提供这样的接口也并不见得我写不出来:
http://www.cppblog.com/kevinlynx/archive/2008/08/20/59451.html
http://www.cppblog.com/kevinlynx/archive/2008/08/13/58684.html
另外,这里的TYPE_LIST机制取自于loki库。佩服哥们有蔑视loki库的魄力。  回复  更多评论   

# re: 实现functor - 增强型的函数指针 2009-07-05 14:19 deadlm

首先,我连现在实现的functor<void, int, char>类型都嫌烦,考虑有没有更加简化的实现方法,这个类的本质就是个指针,还要这么多烦杂的东西干嘛
说到编译器的问题,只要符合c++标准和主要的几个编译器没问题就行,其它的不行那是它本身的实现有问题,谁能考虑那么多,要有人做个基本编译不了什么东西的编译器出来,难道还要顾虑它不成?
任何库的某个实现都是可以“蔑视”的,就如更加历经考验的stl里的string,纯粹就是个指针盒,我就自写了个字符串类代替,而且基本没在自己的代码里再用过std::string,只要能兼容它,性能比它更好,犯得着理它吗  回复  更多评论   

# re: 实现functor - 增强型的函数指针 2009-07-05 14:22 deadlm

还有就是
干嘛要用class lua_binder<R ( P1 )> 这种型式?  回复  更多评论   

# re: 实现functor - 增强型的函数指针 2009-07-05 16:50 Kevin Lynx

@deadlm
functor这种东西本质上确实如你所说是保存一个“函数”指针。其实偏要加上返回值类型以及各个参数的类型,我觉得主要还是哄好编译器。
void func( int );
void func( int, char );
这两个函数在语言层次毕竟属于不同的类型,functor在回调他们时,需要知道传多少个参数。这些信息都需要保存起来。
template在整个C++中完全属于一种花哨的东西,当然不可否认其作用,如果实在烦这些,可以无视这些语言特性。

之所以我不敢“蔑视”STL、LOKI之类的库,是因为我自认能力没到这级别。也许以后我可以。

class lua_binder<R ( P1 )> 是lua_binder的偏特化,因为lua_binder本体只有一个类型参数,所以,不能写:
class lua_binder<R, P1>。这么说来,在支持多参数函数的情况下,要么使用functor<int, TYPE_LIST1( ...的形式,要么使用functor<int (int)>的形式。对于functor<int, int>的形式,你指的是怎样的实现?(很久没在弄模板这些东西,有点生疏)。
  回复  更多评论   

# re: 实现functor - 增强型的函数指针 2009-07-05 22:01 deadlm

整个周日下午就在弄这个,终于搞掂,发现这个实现用来做事件模型更适当

大概因为我最开始选择的语言是pascal,所以我对cc里声明个变量就要一长串字符非常厌烦,纯粹是长久养成的习惯。

嗯。。。。。自己看了自己在上面的第一个贴,好像语气有问题,如果Kevin Lynx 觉得有冒犯,那就不好意思了。  回复  更多评论   

# re: 实现functor - 增强型的函数指针 2009-07-05 22:09 deadlm

其实加几个辅助类进行记录就可以不用class lua_binder<R ( P1 )> 的形式,
当然这是取巧的作法,你这样做才是真正实现无限参数的方法,而且内部实现的时候看起来会非常丑陋,因为代码量实在太多。仅在用起来时看似简洁。
我以前第一次写的时候还写过这样的东西呢
#define MFuncP(...) LLib::Member_Function_Pointer<__VA_ARGS__>::core_b5_c12  回复  更多评论   

# re: 实现functor - 增强型的函数指针 2009-07-05 22:40 Kevin Lynx

@deadlm
没用过pascal,不同语言带来的感受肯定不同。:)

functor之类的东西,为了支持各种不同类型的函数,其内部实现确实很恶心,而且少不了复制代码。后来发现有宏递归这种东西(http://www.cppblog.com/kevinlynx/archive/2008/08/20/59451.html boost中甚至直接有个macro库),虽然内部实现可以少写些代码(用宏来帮助生成),但是其代码看起来更纠结。:D

  回复  更多评论   

# re: 实现functor - 增强型的函数指针 2009-07-05 23:51 deadlm

睡前再说几句:
我现在实现的无需声明参数函数指针的内部实现才叫恶心呢,先是用了八九个完全不同的辅助类,然后每个辅助类还重复了N次,这些还不是最恶心的,最恶心还是因为要进行类型弱化,我是直接查询了模板特化的内存序列,硬着上的,那部分代码我现在看到就有全部选择然后按下delete键的冲动。
只在vc和gcc上试过,还没在其它编译测试过,都不知道行不行。

完事后想起一件事,以前用过php,里面的函数指针好像就是无类型的,是不是应该找它的源码剽上一剽呢?  回复  更多评论   

# re: 实现functor - 增强型的函数指针 2009-07-07 16:13 deadlm

测试过后,终于出问题,不支持返回值为void。。。。。。
结果只能改成这样
funcbp fp;
fp.set(testfunc,a);
fp();
本来找到你的blog就是想剽上一剽的。。。。。。。嘿嘿
结果找了不少地方,你这个还算离我的目的最相近的,其它的很多实现更离谱
离完全的无类型函数指针就差一步啊,就因为void不能引用。。。。。。
算了,不折腾了,拜拜
  回复  更多评论   

# re: 实现functor - 增强型的函数指针 2009-07-08 10:30 Kevin Lynx

@deadlm
- -|
  回复  更多评论   

# re: 实现functor - 增强型的函数指针 2010-02-27 16:05 G_cofa

呵呵,好。  回复  更多评论   

# re: 实现functor - 增强型的函数指针 2010-06-02 14:55 欣萌

这是我看过的最好的。  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理