一:何为函数对象(function object)
二:为什么函数对象
三:函数对象的用法
四:深入函数对象
一:何为函数对象
函数对象故名思议就是一个表示函数调用的对象,这个的对象可以像函数一般的被调用。在c语言中我们用一个函数指针表示一个函数,然后调用这个指针便可以调用函数:
//定义一个参数是两个int,返回值是一个int的函数指针类型function
typedef int (*function)(int a,int b);
int sum(int a,int b)
{
return a+b;
}
//要求传入一个函数指针,这个指针指向的函数的传入参数和返回值是一个符合function函数指针
int to_use_function(function func)
{ ...........
//调用这个函数指针指向的函数
return func(1,2);
}
int main(int argc,char* argv[])
{
//传入函数指针sum
int i=to_use_function(sum);
}
c语言另外一种函数调用的形式就是在struct保存函数指针和函数指针需要的传入参数,这中形式的缺陷就是struct之中的参数与方法无法绑定,需要在调用这个struct的方法里从struct中取出参数传给函数指针进行调用才行,类似下面这种情况:
int sum(int a,int b)
{
return a+b;
}
struct callback
{
int x,y;
int (*func)(int,int);//函数指针
};
void to_use_function(struct callback* call)
{
//调用结构体中的函数,需要自己将结构体中的参数取出
printf("%d",call->func(call->x,call->y));
}
int main(int argc,char* argv[])
{
struct callback call;
call.func=sum;
call.x=1;
call.y=2;
to_use_function(&call);
}
上面的是C语言的做法。在c++中,我们可以做得更多。
看一下这样一个情况,STL算法中查找特定值的算法声明如下:
template< class InputIt, class UnaryPredicate >
InputIt find_if( InputIt first, InputIt last, UnaryPredicate p );
其中first表明查找起点,last表明结束地点,UnaryPredicate则是一个用来对元素比较的
型别,该型别支持一个
bool
pred
(
const
Type
&
a
)操作
.其中type为iterator的关联值类型。只要提供该操作就能通过c++的型别推倒。
1.可以直接传入一个函数指针,只要该函数型别和上述需求型别一致就行。
2.可以通过重载"()"操作符,提供一个类对象,该类通过重载操作符之后也能满足上述的型别推倒。如下:
template<typename type>class Predicate
{
public:
Predicate(const type& t):value_(t)
{}
bool operator()(const type& t) const //这里括号里面与外面的const都可以不要,但是要保证方法不会改变t的值
{
if(t==value_)
{
return true;
}
return false;
}
private:
type value_;
};
.......................
.......................
find_if(container.begin(),container.end(),Predicate<int>(5));//生成一个函数对象,假设这里的容器的关联值类型是int.
上述Predicate其实就是一个典型的函数对象,这个函数对象为inline类型所以调用速度不用担心,而且相对于先前的两种c语言的指针和struct类型的函数调用来说更为灵活(注意却分c语言和c++的结构体区别,别弄混了)。具体灵活在什么地方,就是这个对象里面可以保存相关的状态值。在本例中状态值就是指的value_。
虽然class声明的function object更为灵活,但是不得不说function object concept的定义是只要能对这个型别调用operator(),它便是function object。所以函数对象并不一定是对象,它也可以是指针,或其他只要满足function object concept的条件的型别。
二:为什么函数对象
在上面的例子中如果我们不使用函数对象,而用函数指针我们就不得不改变find_if算法的定义,使它接受一个额外的value,或者将要进行比较的值硬编码在我们传入的函数内。这不仅减少了代码的“泛型”,更削弱了第三个传入参数的灵活性。(因为我们原本可以用函数对象存储的状态值实现更多更为灵活的比较)。
上述一点是使用对象来写函数对象的最大优点,利用对象内部可以保存值,我们可以实现很多更为强大的功能,与各种STL的算法结合起来可以省去不少重复编码的时间。
三:如何使用函数对象
1.函数对象的型别(concepts)
掌握函数对象的使用,就不得不理解STL中算法对函数对象的各种“要求”。STL中将这些要求归纳总结出了三个concepts。Generator(无参函数),Unary Function(一元)和Binary Function(二元函数)。这只是三个最基础的concepts,一般算法所用到的function object concept都会是从这三个基础concepts上面Redefine的Concepts。
Unary Function,Binary Function和Predicate:unary Function 要求传入一个与算法使用的*iterator可以互相隐式转换的输入参数,并返回一个可以赋值给outiterator的参数。而Predicate就是要求返回值类型是bool型,这是一个对unary Function的Refinement的concept。同理BianryPredicate与Binary Function也是一样的关系。
2.函数对象的适配器(adaptor)
首先要弄明白一个概念就是,上面我们说道的Generator,Unary Function,Binary Function和他们对应的adaptable Generator,adaptable Unary Function.....这两种concept之间的关系。adaptable function是前述fcuntion的redefinement,即前述function满足的条件adaptable function都满足,且adaptable function还有一个额外的requirement就是:调用adaptable function的方法能够知道adaptable function的返回型别和引数型别。返回型别即是函数对象"()"操作符的返回类型,引数类型即是"()"的参数的类型。如何实现的在深入函数对象分析。
只要函数对象是adaptable function这个concept的一个model,那么我们便可以对这个函数对象进行适配了。何为适配器?适配器即是把“一种接口转化为另外一种接口的组件”。看下面这个例子:
//首先这是一个binary function concept的model.
//为了让它满足adpatable function concept的requirement,所以让它继承了binary_function
//深入function object中再分析原因
template<class type> class myequal:public binary_function<type,type,bool>
{
public:
//重载了()方法,这里需要方法限定为const也是满足adaptable function的requirment
bool operator()(const type& a,const type& b) const
{
if(a==b)
return true;
return false;
}
};
int main ()
{
//调用了binder1st这个adaptor,我们让myequal这个binary function object适配成了unary function
binder1st < myequal<int> > equal_1 (myequal<int>(),1);
int number[] = {1,2,3,4,5,1};
int cx = count_if (number,number+6,equal_1);
cout << "在number中有 " << cx << " 数字等10.\n";
return 0;
}
在这个例子中我们定义了一个adaptable binary function的model "myequal " ,接着我们调用了binder1st 这个适配器,适配出了一个unary function concept model的一个对象,然后在count_if算法中传入了这个对象得到了我们想要的结果。
我们再深入一点,其实myequal是一个adaptable binary predicate function的model,而binder这个适配器适配出了一个adaptable unary predicate function的model。具体怎么适配的下一小节再输入分析。
常用的adaptor就binder1st,binder2nd,pointer_to_XXX_function系列(把函数指针封装到adaptable的function object),mem_fun系列(把成员函数指针封装到adaptable的function object中,()接受的参数包含一个改成员函数类的对象)
3.adaptor function
如果我们直接使用adaptor会写一大堆,用起来不舒服,看起来也很糟糕,STL提供了一些adaptor类对应的adaptor function,这些function只是让调用这些adaptor看起来更简单而已,它们的实现往往只有一条语句,就是调用对应的adaptor的构造函数,实质上没有做任何多余的工作。如binder1st对应的adaptor function定义如下:
template <class Operation, class T>
binder1st<Operation> bind1st (const Operation& op, const T& x)
{
//这里只是简单的调用了构造函数而已,没有做多余的事情
return binder1st<Operation>(op, typename Operation::first_argument_type(x));
}
前面我们讲的函数适配器都有各自对应的适配器方法:binder1st->bind1st,binder2nd->bind2nd,pointer_to_xxx_function->ptr_fun....
template <class Operation, class T>
binder1st<Operation> bind1st (const Operation& op, const T& x)
{
//这里只是简单的调用了构造函数而已,没有做多余的事情
return binder1st<Operation>(op, typename Operation::first_argument_type(x));
}
4.预定义的function object
在STL中有许多预定义的function object,这些function object能很好的和各种STL算法搭配使用,我们不需要自己再去定义function,因为它们能满足大多数的情况。预定义的function object 主要分为三类:
1.基本数值运算类:plus,minus,multiplies,divides,modulus,negate。它们除了negate是adaptable unary function之外,其它都是adaptablebinary function
2.基本比较运算:equal_to,not_equal_to,greater,less,less_equal和greater_equal。他们都是全都是adaptable Predicate binary function
上面的例子如果我们调用预定义的function object 就会变得简单很多:
int main ()
{
int number[] = {1,2,3,4,5,1};
//我们调用了预定义的adaptabl binary function object,然后调用adaptor function将它适配成unary function
int cx = count_if (number, number+6, bind1st(equal_to<int>(),10) );
cout << "在number中有 " << cx << " 数字等10.\n";
return 0;
}
四:深入函数对象
代码分析比较冗长乏味,留待下篇吧