我的C++实践(12):函数指针与仿函数
2016-07-29 00:00
435 查看
C++的函数调用语法实体有函数、类似于函数的宏、函数指针、仿函数(即函数对象)。函数调用的方式有:
(1)直接调用:通过函数名直接调用函数,函数起始地址成为指令的一部分,因此编译期就能确定调用了哪个函数。
(2)间接调用:通过函数指针来调用函数,函数起始地址位于内存或寄存器的某处,因此到运行期才能确定调用哪个函数。
(3)内联调用:直接在调用处展开函数代码,在编译期进行,因此到运行期就不存在函数调用了。
对函数指针和仿函数的使用,基本上都是某种形式的回调。通常的程序库是库作者来编写库的代码,让用户来使用这些库,而回调正好相反,程序库调用了某个函数,而这个函数的代码需要由使用库的用户来编写。在实际应用中,仿函数与函数指针相比有很多优点,因为仿函数是类对象,因此我们可以在仿函数中关联状态信息(当然这会使仿函数类占内存空间,但我们可以使用空基类优化技术),这样根据不同的参数生成不同的函数实例。仿函数可以作为模板的类型实参来传递,也可以作为函数调用实参来传递。但仿函数是类对象,因此不能作为非类型模板实参,但函数指针可以作为非模板模板实参。通过仿函数,我们可以把所有的的普通函数都封装成仿函数类,这样就可以以对象的方式来统一使用各个函数。我们也可以对仿函数的各个参数类型和返回类型进行萃取,从而使其具有反射的能力,不同的仿函数还可以方便地组合起来使用。
编写仿函数类的基本约定: 在仿函数类中要定义仿函数的参数个数常量NumParams、每个参数的类型typedef Param1T,typedef Param2T,...、返回类型tyepdef ReturnT。根据约定,我们的仿函数类应该编写成类似于下面的形式:
1、仿函数的类型萃取实现: FunctorParam<F,N>用来获取仿函数F的第N的参数的类型,当N值不大于实际的参数个数时,调用UsedFunctorParam<F,N>模板针对N的特化来获取相应位置处的参数类型,当N值大于实际的参数个数时,返回私有的无用嵌套成员类 NNest,它不能用来创建对象,因此没有副作用。代码如下:
解释:
(1)UsedFuctorParam<F,N>用来抽取仿函数F的实际使用的第N个参数类型,这里N没有超过仿函数的实际参数个数。参数类型的抽取是通过对每个N值提供一个局部特化来实现的。假设仿函数的参数个数最多为20,对具有工业强度的库而言,其设计一般要达到20个参数左右。实际中很少有多于20个参数的仿函数。这里#undef表示宏FunctorParamSpec的生命期结束,即下面就已经不存在FunctorParamSpec这个宏了,要用的话需要重新定义。
(2)FunctorParam<F,N>中,N可以超过仿函数的实际参数个数,当超过时,就返回一个私有的无用嵌套类NNest,NNest在FunctorParam内部,在外部并不能直接用类名NNest来创建对象,因此对不存在的参数虽然返回了一个类型,但并没有产生副作用。这里用到了类型萃取技术中开发的IfThenElse模板。
2、把函数指针封装成仿函数: 直接对函数指针的返回类型和各个参数类型进行参数化,设计成仿函数类模板。
下面的ForwardParamT<T>用于决定仿函数的参数类型T是传引用还是传原来的普通类型,T为类类型时则传引用,其他情况则传原来的普通类型(这需要用到类型萃取技术中开发的TypeOp模板),这样可以避免昂贵的拷贝构造函数调用。对仿函数参数为空(即为void)的情况,提供一个全局特化,指定空参数的类型为一个无副作用的哑类型。
下面的FunctionPtrT用来根据函数指针的返回类型和各个参数类型构造出函数指针(这里针对最多为5个参数的情况,对含有更多个参数的函数原理和做法是一样的)。最后的FunctionPtr就是真正封装函数指针的仿函数类型,要遵循仿函数的编写约定。它组合一个用FunctionPtrT构造出来的函数指针,然后根据函数指针的不同实参个数(0-5个)重载operator(),并用函数指针来进行调用实际的函数即可。在调用函数时,用到了上面的ForwardParamT<T>,这样就可以在传递调用实参时避免拷贝构造函数的调用。包装函数func_ptr(*fp)用来方便地创建封装了函数指针的函数对象。
测试代码如下:
3、仿函数的组合: Composer<FO1,FO2>用来组合两个仿函数的使用,先调用仿函数FO1(其参数个数可以是0-20个),再把其返回的结果的用作实参来调用仿函数FO2。可见FO2只能有一个参数,且与FO1的返回类型相同,这样才能组合使用。包装函数compose(f1,f2)用来方便地创建两个仿函数的组合对象。
解释:
(1)这里让Composer<FO1,FO2>通过BaseMem间接继承模板参数FO1,FO2,以便当FO1和FO2为空类时可以进行空基类优化。如果Composer直接继承FO1和FO2,当FO1和FO2为同一个类型时,编译会出错,因此我们需要要引入间接层BaseMem,现在的基类BaseMem<FO1,1>和BaseMem<FO2,2>并不是同一个类型。
(2)Composer是仿函数类,需要遵循仿函数的编写约定。它的各个参数类型是FO1的各个参数类型。因此我们需要用FunctorParam来抽取仿函数FO1的各个参数类型,以作为Composer的各个参数Param1T, Param2T,...,Param20T。
(3)构造函数用于组合两个仿函数对象,它需要考虑两个仿函数的const和非const的情况。下面的函数调用运算符中是先调用FO1,把返回结果作为实参再调用FO2。
4、仿函数参数的值绑定: 即将仿函数的某个参数绑定为一个特定的值,这样仿函数的参数就可以减少一个。我们需要开发出一个绑定器(是一个仿函数)来作绑定操作,一个独立的类来存放绑定值。绑定器的各个参数类型萃取也比较麻烦,它需要去掉仿函数中被绑定的参数,然后后面的参数又要前移一个位置,因此我们把绑定器的参数类型萃取抽离出来设计成一个独立的模板。
下面的BoundVal<T>用于在运行期存放T类型的绑定值(这个值后面会被绑定到仿函数的某个参数上去),而StaticBoundVal<T> 则用于在编译期存放T类型的绑定值。
下面的BinderParams<F,P>用来萃取绑定器的参数类型。它只要去掉仿函数中被绑定的参数, 并把后面的参数前移1个位置即可。
下面的SignSelectT<S,NegT,ZeroT,PosT>根据整数S是正、0、还是负来选择相应的类型。
下面是真正的绑定器Binder<FO,P,V>。它把仿函数FO的第P个参数绑定为一个存放在V类型对象中的绑定值,绑定后它还要完成对仿函数FO的调用,即Binder<FO,P,V>本身就相当于被绑定后的FO。Binder的参数类型列表Params是仿函数FO去掉绑定参数后的各个参数类型,可见Binder::operator()的参数个数比底层的FO::operator()少一个,因此它在调用底层的FO::operator()时需要重新确定原来FO的参数,并指定一个参数为绑定值。嵌套类模板ArgSelect<A>就是用来确定FO的各个实参。包装函数bind<P>(fo,val)用于方便地将仿函数对象fo的第P个参数绑定为值val。
解释:
(1)Binder继承自模板参数FO和V,这样就可以使用空基类优化技术。Binder是一个仿函数,因此它要遵循仿函数的编写约定,定义参数个数和各个参数类型(用上面的BinderParams来萃取)。
(2)构造函数根据传过来的仿函数对象和存放了绑定值的V类型对象构造Binder,它需要考虑各种const和非const的情况。而构造函数模板则直接根据仿函数对象和要绑定的值(T类型的)构造Binder,绑定的值存储在BoundVal<T>中,即在运行期存储绑定值。
(3)ArgSelect<A>用来确定原来FO的各个实参,这样才能在Binder中完成对被绑定的FO的调用。它先根据A的位置定义绑定实参前面的类型NoSkipT、绑定实参后面的类型SkipT、以及绑定实参处的类型BindT。然后定义3个不同的嵌套类,里面有一个静态的select函数,用来实现3种情况下对FO实参值的选择。接着根据A与P的相对位置,用SignSelectT得到仿函数FO中A处的参数类型、以及仿函数FO的实参选择类(即上面的的3个嵌套类之一)。最后是真正的实参选择函数from,它调用相应选择类的select,根据实参位置A与P的相对位置,自动选择FO中A处是使用用户提供的实参,还是使用被绑定的值。
(4)函数调用Binder::operator()对调用实参个数不同的情况,都要进行重载。Binder::operator()直接调用FO::operator(),而各个实参是根据所在位置用ArgSelect<n>::from来自动选择,对FO中绑定处的参数用绑值V::get(),对其他位置的参数则用用户传递过来的实参。
(5)包装函数bind<P>(fo,val)直接返回一个对fo绑定后的少了一个参数的仿函数对象。它的使用就相当于对少了一个参数的fo的使用。
测试代码如下:
5、普通函数参数的值绑定: 可先用上面的func_ptr(*fp)把函数(最多只能接受5个参数)封装成仿函数,再用bind来进行值绑定即可。上面的测试代码中就是这样用的。从中我们可以看出,通过使用func_ptr和bind的实现,我们可以对任意普通函数的参数进行值绑定。
(1)直接调用:通过函数名直接调用函数,函数起始地址成为指令的一部分,因此编译期就能确定调用了哪个函数。
(2)间接调用:通过函数指针来调用函数,函数起始地址位于内存或寄存器的某处,因此到运行期才能确定调用哪个函数。
(3)内联调用:直接在调用处展开函数代码,在编译期进行,因此到运行期就不存在函数调用了。
对函数指针和仿函数的使用,基本上都是某种形式的回调。通常的程序库是库作者来编写库的代码,让用户来使用这些库,而回调正好相反,程序库调用了某个函数,而这个函数的代码需要由使用库的用户来编写。在实际应用中,仿函数与函数指针相比有很多优点,因为仿函数是类对象,因此我们可以在仿函数中关联状态信息(当然这会使仿函数类占内存空间,但我们可以使用空基类优化技术),这样根据不同的参数生成不同的函数实例。仿函数可以作为模板的类型实参来传递,也可以作为函数调用实参来传递。但仿函数是类对象,因此不能作为非类型模板实参,但函数指针可以作为非模板模板实参。通过仿函数,我们可以把所有的的普通函数都封装成仿函数类,这样就可以以对象的方式来统一使用各个函数。我们也可以对仿函数的各个参数类型和返回类型进行萃取,从而使其具有反射的能力,不同的仿函数还可以方便地组合起来使用。
编写仿函数类的基本约定: 在仿函数类中要定义仿函数的参数个数常量NumParams、每个参数的类型typedef Param1T,typedef Param2T,...、返回类型tyepdef ReturnT。根据约定,我们的仿函数类应该编写成类似于下面的形式:
class PersonSort{ public: enum(NumParams=2 }; typedef bool ReturnT; typedef Person const& Param1T; typedef Person const& Param2T; bool operator()(Person const& p1,Person const& p2) const{ //返回p1是否小于p2 } };
1、仿函数的类型萃取实现: FunctorParam<F,N>用来获取仿函数F的第N的参数的类型,当N值不大于实际的参数个数时,调用UsedFunctorParam<F,N>模板针对N的特化来获取相应位置处的参数类型,当N值大于实际的参数个数时,返回私有的无用嵌套成员类 NNest,它不能用来创建对象,因此没有副作用。代码如下:
//functorparam.hpp:获取仿函数各个参数的类型 #ifndef FUNCTOR_PARAM_HPP #define FUNCTOR_PARAM_HPP #include "ifthenelse.hpp" //用到了IfThenElse模板 /* * UsedFuctorParam<F,N>:抽取仿函数F的实际使用的第N个参数类型 */ template <typename F, int N> class UsedFunctorParam; //基本模板 //对每个N值,都提供一个局部特化,以获取相应位置处参数的类型 //使用宏FunctorParamSpec(N)来编写UsedFunctorParam的特化,可以使代码更简洁 #define FunctorParamSpec(N) / template<typename F> / class UsedFunctorParam<F, N>{ / public: / typedef typename F::Param##N##T Type; / }; //假设N到20为止,很少有多于20个参数的仿函数 FunctorParamSpec(1) FunctorParamSpec(2) FunctorParamSpec(3) FunctorParamSpec(4) FunctorParamSpec(5) FunctorParamSpec(6) FunctorParamSpec(7) FunctorParamSpec(8) FunctorParamSpec(9) FunctorParamSpec(10) FunctorParamSpec(11) FunctorParamSpec(12) FunctorParamSpec(13) FunctorParamSpec(14) FunctorParamSpec(15) FunctorParamSpec(16) FunctorParamSpec(17) FunctorParamSpec(18) FunctorParamSpec(19) FunctorParamSpec(20) #undef FunctorParamSpec //宏FunctorParamSpec的生命期结束 /* * FunctorParam<F,N>::Type:获取仿函数F的第N个参数的类型,当N大于实际参数 * 个数F::NumParams时,FunctorParam<F,N>::Type会返回私有的无用嵌套类 * NNest,它不能用来创建对象,因此没有副作用 */ template <typename F, int N> class FunctorParam{ private: class Unused{ //嵌套类 private: class NNest {}; //嵌套的无用成员类 public: typedef NNest Type; }; public: //获取相应位置处的参数类型 typedef typename IfThenElse<F::NumParams>=N, UsedFunctorParam<F,N>, Unused>::ResultT::Type Type; }; #endif
解释:
(1)UsedFuctorParam<F,N>用来抽取仿函数F的实际使用的第N个参数类型,这里N没有超过仿函数的实际参数个数。参数类型的抽取是通过对每个N值提供一个局部特化来实现的。假设仿函数的参数个数最多为20,对具有工业强度的库而言,其设计一般要达到20个参数左右。实际中很少有多于20个参数的仿函数。这里#undef表示宏FunctorParamSpec的生命期结束,即下面就已经不存在FunctorParamSpec这个宏了,要用的话需要重新定义。
(2)FunctorParam<F,N>中,N可以超过仿函数的实际参数个数,当超过时,就返回一个私有的无用嵌套类NNest,NNest在FunctorParam内部,在外部并不能直接用类名NNest来创建对象,因此对不存在的参数虽然返回了一个类型,但并没有产生副作用。这里用到了类型萃取技术中开发的IfThenElse模板。
2、把函数指针封装成仿函数: 直接对函数指针的返回类型和各个参数类型进行参数化,设计成仿函数类模板。
下面的ForwardParamT<T>用于决定仿函数的参数类型T是传引用还是传原来的普通类型,T为类类型时则传引用,其他情况则传原来的普通类型(这需要用到类型萃取技术中开发的TypeOp模板),这样可以避免昂贵的拷贝构造函数调用。对仿函数参数为空(即为void)的情况,提供一个全局特化,指定空参数的类型为一个无副作用的哑类型。
//forwardparam.hpp:ForwardParamT<T>用于决定仿函数的参数类型T是传引用还是传原来的普通类型 #ifndef FORWARD_PARAM_HPP #define FORWARD_PARAM_HPP #include "ifthenelse.hpp" #include "isclasst.hpp" #include "typeop.hpp" //用于决定函数的参数是传引用还是传原来的类型 template<typename T> class ForwardParamT{ //基本模板:T为类类型则传const引用,否则就传普通类型 public: typedef typename IfThenElse<IsClassT<T>::Yes, typename TypeOp<T>::RefConstT, typename TypeOp<T>::ArgT >::ResultT Type; }; template<> class ForwardParamT<void>{ //全局特化:void表示函数参数为空,则传哑类型Ununsed, private: //它不能创建对象,因此没有副作用 class Unused {}; public: typedef Unused Type; }; #endif
下面的FunctionPtrT用来根据函数指针的返回类型和各个参数类型构造出函数指针(这里针对最多为5个参数的情况,对含有更多个参数的函数原理和做法是一样的)。最后的FunctionPtr就是真正封装函数指针的仿函数类型,要遵循仿函数的编写约定。它组合一个用FunctionPtrT构造出来的函数指针,然后根据函数指针的不同实参个数(0-5个)重载operator(),并用函数指针来进行调用实际的函数即可。在调用函数时,用到了上面的ForwardParamT<T>,这样就可以在传递调用实参时避免拷贝构造函数的调用。包装函数func_ptr(*fp)用来方便地创建封装了函数指针的函数对象。
//functionptr.hpp:把最多为5个参数的函数指针类型封装成仿函数模板FunctionPtr<RT,P1,P2,P3,P4,P5> //对含有更多个参数的函数,其原理和做法一样的 #ifndef FUNCTION_PTR_HPP #define FUNCTION_PTR_HPP #include "forwardparam.hpp" //用于决定函数的形参T是传引用还是传原来的普通类型 /* * FunctionPtrT<RT,P1,P2,P3,P4,P5>:用来根据返回类型和各个参数类型构造出函数指针 */ template<typename RT, typename P1 = void, typename P2 = void, typename P3 = void, typename P4 = void, typename P5 = void> class FunctionPtrT{ //基本模板:函数指针有5个参数的情况 public: enum { NumParams = 5 }; //定义参数个数的枚举量 typedef RT (*Type)(P1,P2,P3,P4,P5); //根据参数类型来定义函数指针 }; template<typename RT, typename P1, typename P2, typename P3, typename P4> class FunctionPtrT<RT,P1,P2,P3,P4,void>{ //局部特化:4个参数的情况 public: enum { NumParams = 4 }; typedef RT (*Type)(P1,P2,P3,P4); }; template<typename RT, typename P1, typename P2, typename P3> class FunctionPtrT<RT,P1,P2,P3,void,void>{ //局部特化:3个参数的情况 public: enum { NumParams = 3 }; typedef RT (*Type)(P1,P2,P3); }; template<typename RT, typename P1, typename P2> class FunctionPtrT<RT,P1,P2,void,void,void>{ //局部特化:2个参数的情况 public: enum { NumParams = 2 }; typedef RT (*Type)(P1,P2); }; template<typename RT, typename P1> class FunctionPtrT<RT,P1,void,void,void,void>{ //局部特化:1个参数的情况 public: enum { NumParams = 1 }; typedef RT (*Type)(P1); }; template<typename RT> class FunctionPtrT<RT,void,void,void,void,void>{ //局部特化:0个参数的情况 public: enum { NumParams = 0 }; typedef RT (*Type)(); }; /* * FunctionPtr<RT,P1,P2,P3,P4,P5>:封装函数指针的仿函数类型 * 使用缺省的void时表示该参数为空 */ template<typename RT, typename P1 = void, typename P2 = void, typename P3 = void, typename P4 = void, typename P5 = void> class FunctionPtr{ private: //获得相应的函数指针类型 typedef typename FunctionPtrT<RT,P1,P2,P3,P4,P5>::Type FuncPtr; FuncPtr fptr; //被封装的函数指针 public: //保存好参数个数及其各个参数类型 enum { NumParams = FunctionPtrT<RT,P1,P2,P3,P4,P5>::NumParams }; typedef RT ReturnT; typedef P1 Param1T; typedef P2 Param2T; typedef P3 Param3T; typedef P4 Param4T; typedef P5 Param5T; FunctionPtr(FuncPtr ptr):fptr(ptr) { //构造函数,创建函数对象,它封装了函数指针 } // 下面是函数调用 RT operator()() { //0个参数的情况 return fptr(); } RT operator()(typename ForwardParamT<P1>::Type a1){ //1个参数的情况 return fptr(a1); } RT operator()(typename ForwardParamT<P1>::Type a1, typename ForwardParamT<P2>::Type a2){ //2个参数的情况 return fptr(a1, a2); } RT operator()(typename ForwardParamT<P1>::Type a1, typename ForwardParamT<P2>::Type a2, typename ForwardParamT<P3>::Type a3){ //3个参数的情况 return fptr(a1, a2, a3); } RT operator()(typename ForwardParamT<P1>::Type a1, typename ForwardParamT<P2>::Type a2, typename ForwardParamT<P3>::Type a3, typename ForwardParamT<P4>::Type a4){ //4个参数的情况 return fptr(a1,a2,a3,a4); } RT operator()(typename ForwardParamT<P1>::Type a1, typename ForwardParamT<P2>::Type a2, typename ForwardParamT<P3>::Type a3, typename ForwardParamT<P4>::Type a4, typename ForwardParamT<P5>::Type a5){ //5个参数的情况 return fptr(a1,a2,a3,a4,a5); } }; /* * func_ptr(*fp):包装函数,用于方便地创建封装了函数指针的函数对象 */ template<typename RT> inline FunctionPtr<RT> func_ptr(RT (*fp)()){ //0个参数的情况 return FunctionPtr<RT>(fp); } template<typename RT, typename P1> inline FunctionPtr<RT,P1> func_ptr(RT (*fp)(P1)){ //1个参数的情况 return FunctionPtr<RT,P1>(fp); } template<typename RT, typename P1, typename P2> inline FunctionPtr<RT,P1,P2> func_ptr(RT (*fp)(P1,P2)){ //2个参数的情况 return FunctionPtr<RT,P1,P2>(fp); } template<typename RT, typename P1, typename P2, typename P3> inline FunctionPtr<RT,P1,P2,P3> func_ptr(RT (*fp)(P1,P2,P3)){ //3个参数的情况 return FunctionPtr<RT,P1,P2,P3>(fp); } template<typename RT, typename P1, typename P2, typename P3,typename P4> inline FunctionPtr<RT,P1,P2,P3,P4> func_ptr(RT (*fp)(P1,P2,P3,P4)){ //4个参数的情况 return FunctionPtr<RT,P1,P2,P3,P4>(fp); } template<typename RT, typename P1, typename P2, typename P3,typename P4,typename P5> inline FunctionPtr<RT,P1,P2,P3,P4,P5> func_ptr(RT (*fp)(P1,P2,P3,P4,P5)){ //5个参数的情况 return FunctionPtr<RT,P1,P2,P3,P4,P5>(fp); } #endif
测试代码如下:
//functionptrtest.cpp:对封装函数指针的仿函数进行测试 #include <iostream> #include <string> #include <typeinfo> #include "functionptr.hpp" double seven(){ //0个参数的情况 return 7.0; } std::string more(){ //0个参数的情况 return std::string("more"); } int display(int a,int b){ //2个参数的情况 std::cout<<a<<","<<b<<std::endl; return 0; } int output(int a,double b,std::string c){ //3个参数的情况 std::cout<<a<<","<<b<<","<<c<<std::endl; return 0; } template <typename FunctorT> void demo(FunctorT func){ //func是封装了函数指针的函数对象,这个函数指针有0个参数 std::cout << "Functor returns type: " << typeid(typename FunctorT::ReturnT).name() << '/n' << "Functor returns value: " << func() << '/n'; } int main(){ demo(func_ptr(seven)); demo(func_ptr(more)); func_ptr(display)(123,456); func_ptr(output)(83,3.14159,"Jack Zhou"); return 0; }
3、仿函数的组合: Composer<FO1,FO2>用来组合两个仿函数的使用,先调用仿函数FO1(其参数个数可以是0-20个),再把其返回的结果的用作实参来调用仿函数FO2。可见FO2只能有一个参数,且与FO1的返回类型相同,这样才能组合使用。包装函数compose(f1,f2)用来方便地创建两个仿函数的组合对象。
//composer.hpp:Composer<FO1,FO2>用于组合两个仿函数的使用 #ifndef COMPOSER_HPP #define COMPOSER_HPP #include "forwardparam.hpp" //用于决定函数的形参T是传引用还是传原来的普通类型 #include "functorparam.hpp" //用于获取仿函数各个参数的类型 /* * BaseMem<C,N>:为了避免Composer多次继承同一个类而引入的间接层 */ template <typename C, int N> class BaseMem : public C { //直接继承模板参数C(是一个仿函数类) public: BaseMem(C& c) : C(c) { } BaseMem(C const& c) : C(c) { } }; /* * Composer<FO1,FO2>:组合两个仿函数FO1、FO2,先调用FO1,再调用FO2 * 通过BaseMem间接继承FO1和FO2,这样当FO1或FO2为空类是就可以进行空基类优化 */ template <typename FO1, typename FO2> class Composer : private BaseMem<FO1,1>, private BaseMem<FO2,2>{ public: enum { NumParams = FO1::NumParams }; //参数个数 typedef typename FO2::ReturnT ReturnT; //最后的返回类型 //用FunctorParam来获取仿函数FO1的各个参数类型, //作为Composer的各个参数Param1T, Param2T,... // 用宏来简化参数类型的定义 #define ComposeParamT(N) / typedef typename FunctorParam<FO1, N>::Type Param##N##T; //假设N到20为止,很少有多于20个参数的仿函数 ComposeParamT(1) ComposeParamT(2) ComposeParamT(3) //... ComposeParamT(20) #undef ComposeParamT //构造函数:组合两个仿函数对象,这里考虑了各种const和非const的情况 //把两个仿函数对象存储在基类子对象中,这就允许进行空基类优化 Composer(FO1 const& f1, FO2 const& f2) : BaseMem<FO1,1>(f1), BaseMem<FO2,2>(f2) { } Composer(FO1 const& f1, FO2& f2) : BaseMem<FO1,1>(f1), BaseMem<FO2,2>(f2) { } Composer(FO1& f1, FO2 const& f2) : BaseMem<FO1,1>(f1), BaseMem<FO2,2>(f2) { } Composer(FO1& f1, FO2& f2) : BaseMem<FO1,1>(f1), BaseMem<FO2,2>(f2) { } //针对0个参数的函数调用 ReturnT operator() (){ //针对0个参数的函数调用 return BaseMem<FO2,2>::operator()(BaseMem<FO1,1>::operator()()); } //针对1个参数的函数调用 ReturnT operator() (typename ForwardParamT<Param1T>::Type v1) { return BaseMem<FO2,2>::operator()(BaseMem<FO1,1>::operator()(v1)); } //针对2个参数的函数调用 ReturnT operator() (typename ForwardParamT<Param1T>::Type v1, typename ForwardParamT<Param2T>::Type v2) { return BaseMem<FO2,2>::operator()(BaseMem<FO1,1>::operator()(v1, v2)); } //针对3个参数的函数调用 ReturnT operator() (typename ForwardParamT<Param1T>::Type v1, typename ForwardParamT<Param2T>::Type v2, typename ForwardParamT<Param3T>::Type v3) { return BaseMem<FO2,2>::operator()(BaseMem<FO1,1>::operator()(v1, v2,v3)); } //... //一直写到针对20个参数的函数调用 }; /* * compose(f1,f2)包装函数:用于方便地创建两个仿函数的组合 */ template <typename FO1, typename FO2> inline Composer<FO1,FO2> compose(FO1 f1, FO2 f2) { return Composer<FO1,FO2> (f1, f2); } #endif
//composertest.cpp:对仿函数组合的测试 #include <iostream> #include "composer.hpp" #include "functionptr.hpp" double add(double a, double b){ return a+b; } double twice(double a){ return 2*a; } int main(){ std::cout << "compute (20+7)*2: " << compose(func_ptr(add),func_ptr(twice))(20,7) << '/n'; return 0; }
解释:
(1)这里让Composer<FO1,FO2>通过BaseMem间接继承模板参数FO1,FO2,以便当FO1和FO2为空类时可以进行空基类优化。如果Composer直接继承FO1和FO2,当FO1和FO2为同一个类型时,编译会出错,因此我们需要要引入间接层BaseMem,现在的基类BaseMem<FO1,1>和BaseMem<FO2,2>并不是同一个类型。
(2)Composer是仿函数类,需要遵循仿函数的编写约定。它的各个参数类型是FO1的各个参数类型。因此我们需要用FunctorParam来抽取仿函数FO1的各个参数类型,以作为Composer的各个参数Param1T, Param2T,...,Param20T。
(3)构造函数用于组合两个仿函数对象,它需要考虑两个仿函数的const和非const的情况。下面的函数调用运算符中是先调用FO1,把返回结果作为实参再调用FO2。
4、仿函数参数的值绑定: 即将仿函数的某个参数绑定为一个特定的值,这样仿函数的参数就可以减少一个。我们需要开发出一个绑定器(是一个仿函数)来作绑定操作,一个独立的类来存放绑定值。绑定器的各个参数类型萃取也比较麻烦,它需要去掉仿函数中被绑定的参数,然后后面的参数又要前移一个位置,因此我们把绑定器的参数类型萃取抽离出来设计成一个独立的模板。
下面的BoundVal<T>用于在运行期存放T类型的绑定值(这个值后面会被绑定到仿函数的某个参数上去),而StaticBoundVal<T> 则用于在编译期存放T类型的绑定值。
//boundval.hpp:存放绑定值的模板,可通过get()来获取这个绑定值 #ifndef BOUND_VAL_HPP #define BOUND_VAL_HPP #include "typeop.hpp" template <typename T> class BoundVal{ //在运行期存放T类型的绑定值 private: T value; //存放绑定值的对象 public: typedef T ValueT; BoundVal(T v):value(v){ //把要绑定的值通过构造函数传进来 } typename TypeOp<T>::RefT get(){ //设置或获取绑定的值 return value; //返回非const的引用 } }; template <typename T, T Val> class StaticBoundVal{ //在编译期存放T类型的绑定值 public: typedef T ValueT; T get(){ //通过get()来获取绑定的值 return Val; } }; #endif
下面的BinderParams<F,P>用来萃取绑定器的参数类型。它只要去掉仿函数中被绑定的参数, 并把后面的参数前移1个位置即可。
//binderparams.hpp:绑定器的参数类型列表,去掉仿函数中被绑定的参数, //并把后面的参数前移1个位置即可 #ifndef BINDER_PARAMS_HPP #define BINDER_PARAMS_HPP #include "ifthenelse.hpp" /* * BinderParams<F,P>:用于将被绑定参数(即第P个参数)后面的参数前移1个位置 * BinderParams<F,P>::ParamNT:仿函数F被绑定后的第N个参数类型 */ template<typename F, int P> class BinderParams{ public: enum { NumParams = F::NumParams-1 };//一个参数已绑定,故参数个数减1 //将被绑定参数(即第P个参数)后面的参数前移1个位置 #define ComposeParamT(N) / typedef typename IfThenElse<(N<P), FunctorParam<F, N>, / FunctorParam<F, N+1> / >::ResultT::Type / Param##N##T; //假设N到20为止,很少有多于20个参数的仿函数 ComposeParamT(1) ComposeParamT(2) ComposeParamT(3) //... ComposeParamT(20) #undef ComposeParamT }; #endif
下面的SignSelectT<S,NegT,ZeroT,PosT>根据整数S是正、0、还是负来选择相应的类型。
//signselect.hpp:根据整数S是正、0、还是负来选择相应的类型 #ifndef SIGN_SELECT_HPP #define SIGN_SELECT_HPP #include "ifthenelse.hpp" template <int S, typename NegT, typename ZeroT, typename PosT> struct SignSelectT{ typedef typename IfThenElse<(S<0), NegT, typename IfThenElse<(S>0), PosT, ZeroT >::ResultT >::ResultT ResultT; }; #endif
下面是真正的绑定器Binder<FO,P,V>。它把仿函数FO的第P个参数绑定为一个存放在V类型对象中的绑定值,绑定后它还要完成对仿函数FO的调用,即Binder<FO,P,V>本身就相当于被绑定后的FO。Binder的参数类型列表Params是仿函数FO去掉绑定参数后的各个参数类型,可见Binder::operator()的参数个数比底层的FO::operator()少一个,因此它在调用底层的FO::operator()时需要重新确定原来FO的参数,并指定一个参数为绑定值。嵌套类模板ArgSelect<A>就是用来确定FO的各个实参。包装函数bind<P>(fo,val)用于方便地将仿函数对象fo的第P个参数绑定为值val。
//binder.hpp:用于对仿函数的参数进行值绑定 #ifndef BINDER_HPP #define BINDER_HPP #include "ifthenelse.hpp" #include "typeop.hpp" #include "boundval.hpp" //用于存放要绑定的值 #include "forwardparam.hpp" //用于决定仿函数的形参T是传引用还是传原来的普通类型 #include "functorparam.hpp" //获取仿函数各个参数的类 #include "binderparams.hpp" //用于将被绑定参数后面的参数前移1个位置 #include "signselect.hpp" //根据整数的正负性来选择相应的类型 /* * Binder<FO,P,V>:把仿函数FO的第P个参数绑定为一个存放在V类型对象中的绑定值, * V是BoundVal<T>或StaticBoundVal<T,val>类型,里面存放了真正 * 的绑定值(是T类型的) */ template <typename FO, int P, typename V> class Binder : private FO, private V{ public: enum { NumParams = FO::NumParams-1 }; //一个参数已经绑定,故参数个数减1 typedef typename FO::ReturnT ReturnT; //返回类型 typedef BinderParams<FO, P> Params; //参数类型列表:Params中含有仿函数FO //去掉绑定参数后的各个参数类型 //定义Binder需要的各个参数类型(或者为原来类型的const引用,或者为原来的普通类型) #define ComposeParamT(N) / typedef typename ForwardParamT<typename Params::Param##N##T>::Type / Param##N##T; ComposeParamT(1) ComposeParamT(2) ComposeParamT(3) //... ComposeParamT(20) #undef ComposeParamT //构造函数:根据传过来的仿函数对象和存放了绑定值的V类型对象构造Binder, //这里考虑了各种const和非const的情况 Binder(FO& f): FO(f) {} Binder(FO& f, V& v): FO(f), V(v) {} Binder(FO& f, V const& v): FO(f), V(v) {} Binder(FO const& f): FO(f) {} Binder(FO const& f, V& v): FO(f), V(v) {} Binder(FO const& f, V const& v): FO(f), V(v) {} //构造函数模板:直接根据仿函数对象和要绑定的值(T类型的)构造Binder,绑定的值存储在 //BoundVal<T>中,即在运行期存储绑定值 template<class T> Binder(FO& f, T& v): FO(f), V(BoundVal<T>(v)) {} template<class T> Binder(FO& f, T const& v): FO(f), V(BoundVal<T const>(v)) {} //函数调用:对调用实参个数不同的情况,都要重载调用运算符 //Binder::operator()的参数个数比底层的FO::operator()少一个 ReturnT operator()(){ return FO::operator()(V::get()); } ReturnT operator()(Param1T v1){ //根据实参位置(1和2)与P的相对位置,用from函数自动选择是使用用户 //提供的v1,还是使用被绑定的值V::get() return FO::operator()(ArgSelect<1>::from(v1,v1,V::get()), ArgSelect<2>::from(v1,v1,V::get())); } ReturnT operator()(Param1T v1, Param2T v2){ return FO::operator()(ArgSelect<1>::from(v1,v1,V::get()), ArgSelect<2>::from(v1,v2,V::get()), ArgSelect<3>::from(v2,v2,V::get())); } ReturnT operator()(Param1T v1, Param2T v2, Param3T v3) { return FO::operator()(ArgSelect<1>::from(v1,v1,V::get()), ArgSelect<2>::from(v1,v2,V::get()), ArgSelect<3>::from(v2,v3,V::get()), ArgSelect<4>::from(v3,v3,V::get())); } //... //一直写到针对20个参数的函数调用 private: template<int A> class ArgSelect{ //私有的嵌套类模板:实参选择器,用来确定FO原来的各个实参 public: //定义绑定实参前面的类型:FO的第A个参数类型ArgSelect<A>::NoSkipT恰为 //Binder的第A个参数类型(取其引用类型),当A达到比NumParams多1时,退回到Binder的A-1处 typedef typename TypeOp< typename IfThenElse<(A<=Params::NumParams), FunctorParam<Params, A>, FunctorParam<Params, A-1> >::ResultT::Type>::RefT NoSkipT; //定义绑定实参后面的类型:FO的第A个参数类型ArgSelect<A>::SkipT恰为 //Binder的第A-1个参数类型(取其引用类型),当A为1时,前移到Bindder的A处 //ArgSelect<A>::NoSkipT typedef typename TypeOp< typename IfThenElse<(A>1), FunctorParam<Params, A-1>, FunctorParam<Params, A> >::ResultT::Type>::RefT SkipT; //定义绑定实参处的类型:在存放绑定值的V类型中有记录 typedef typename TypeOp<typename V::ValueT>::RefT BindT; //3个不同的嵌套类,用来实现3种情况下FO实参值的选择 class NoSkip{ //选择绑定参数前面的实参:为用户指定的实参 public: static NoSkipT select(SkipT prev_arg, NoSkipT arg, BindT bound_val) { return arg; } }; class Skip{ //选择绑定参数后面的实参:为用户指定的实参 public: static SkipT select(SkipT prev_arg, NoSkipT arg, BindT bound_val){ return prev_arg; } }; class Bind{ //选择绑定参数处的实参:为绑定值 public: static BindT select(SkipT prev_arg, NoSkipT arg, BindT bound_val){ return bound_val; } }; //根据A与P的相对位置返回仿函数FO中A处的参数类型 typedef typename SignSelectT<A-P, NoSkipT, BindT, SkipT>::ResultT ReturnT; //根据A与P的相对位置返回仿函数FO的实参选择类(上面的3个嵌套类之一,用来 //选择FO在A处的实参值) typedef typename SignSelectT<A-P, NoSkip, Bind, Skip>::ResultT SelectedT; //实际的选择函数: static ReturnT from (SkipT prev_arg, NoSkipT arg, BindT bound_val){ //根据获得的选择类,调用select来选择相应的实参值 return SelectedT::select(prev_arg, arg, bound_val); } }; }; /* * 包装函数bind<P>(fo,val):将仿函数对象fo的第P个参数绑定为值val * FO可演绎出来,FunctorParam<FO,P>::Type为绑定值的类型 * BoundVal<typename FunctorParam<FO,P>::Type>为存放绑定值的V类型 * ForwardParamT可用来决定val是传引用还是传原来的类型 */ template <int P,typename FO> //P为绑定参数的位置,FO为仿函数 inline Binder<FO,P,BoundVal<typename FunctorParam<FO,P>::Type> > bind(FO const& fo, typename ForwardParamT<typename FunctorParam<FO,P>::Type>::Type val){ //返回一个对fo进行绑定后的Binder仿函数对象 return Binder<FO, P, BoundVal<typename FunctorParam<FO,P>::Type> >(fo,BoundVal<typename FunctorParam<FO,P>::Type>(val)); } #endif
解释:
(1)Binder继承自模板参数FO和V,这样就可以使用空基类优化技术。Binder是一个仿函数,因此它要遵循仿函数的编写约定,定义参数个数和各个参数类型(用上面的BinderParams来萃取)。
(2)构造函数根据传过来的仿函数对象和存放了绑定值的V类型对象构造Binder,它需要考虑各种const和非const的情况。而构造函数模板则直接根据仿函数对象和要绑定的值(T类型的)构造Binder,绑定的值存储在BoundVal<T>中,即在运行期存储绑定值。
(3)ArgSelect<A>用来确定原来FO的各个实参,这样才能在Binder中完成对被绑定的FO的调用。它先根据A的位置定义绑定实参前面的类型NoSkipT、绑定实参后面的类型SkipT、以及绑定实参处的类型BindT。然后定义3个不同的嵌套类,里面有一个静态的select函数,用来实现3种情况下对FO实参值的选择。接着根据A与P的相对位置,用SignSelectT得到仿函数FO中A处的参数类型、以及仿函数FO的实参选择类(即上面的的3个嵌套类之一)。最后是真正的实参选择函数from,它调用相应选择类的select,根据实参位置A与P的相对位置,自动选择FO中A处是使用用户提供的实参,还是使用被绑定的值。
(4)函数调用Binder::operator()对调用实参个数不同的情况,都要进行重载。Binder::operator()直接调用FO::operator(),而各个实参是根据所在位置用ArgSelect<n>::from来自动选择,对FO中绑定处的参数用绑值V::get(),对其他位置的参数则用用户传递过来的实参。
(5)包装函数bind<P>(fo,val)直接返回一个对fo绑定后的少了一个参数的仿函数对象。它的使用就相当于对少了一个参数的fo的使用。
测试代码如下:
//bindertest.cpp:对绑定器Binder的测试 #include <string> #include <iostream> #include "functionptr.hpp" #include "binder.hpp" //这个函数会被func_ptr封装为仿函数 bool func(std::string const& str, double d, float f){ std::cout << str << ": " << d << (d<f? "<": ">=") << f << '/n'; return d<f; } int main(){ //把仿函数func_ptr(func)的第1个参数绑定为"Comparing" bool result = bind<1>(func_ptr(func), "Comparing")(1.0, 2.0); std::cout << "bound function returned " << result << '/n'; return 0; }
5、普通函数参数的值绑定: 可先用上面的func_ptr(*fp)把函数(最多只能接受5个参数)封装成仿函数,再用bind来进行值绑定即可。上面的测试代码中就是这样用的。从中我们可以看出,通过使用func_ptr和bind的实现,我们可以对任意普通函数的参数进行值绑定。
相关文章推荐
- 我的C++实践(16):引用计数实现
- 我的C++实践(14):限制类对象的个数
- 我的C++实践(10):智能指针
- C++ Primer学习系列(4):关联容器/泛型算法/类
- C++ Primer学习系列(3):函数/标准IO库/顺序容器
- 我的C++实践(7):模板元编程实战
- SQLite剖析(3):C/C++接口介绍
- 我的C++实践(14):限制类对象的个数
- 我的C++实践(3):用多态机制来做设计
- 我的C++实践(10):智能指针
- 我的C++实践(18):多态的双重分派实现
- C++ Primer学习系列(4):关联容器/泛型算法/类
- C++ Primer学习系列(3):函数/标准IO库/顺序容器
- JackZhou的负责任书评:C++ Primer中文版(第4版)
- C++ Primer学习系列(1):快速入门/变量和基本类型/标准库类型
- 搭建Eclipse C/C++开发环境
- 我的C++实践(11):存放异类对象的元组类型
- 我的C++实践(7):模板元编程实战
- SQLite剖析(3):C/C++接口介绍
- 我的C++实践(3):用多态机制来做设计