1.5函数对象(function object)-1.5.1-定义函数对象

一个对象只要像函数一样可以调用,那么它便是函数对象,它与普通函数相比更加通用,同时函数对象还可以拥有状态。标准库<functional>里提供了一些常用的函数对象,并且算法部分<algorithm>大多要求以更加通用的函数对象形式提供,而不仅仅仅局限于普通函数(函数指针)

函数对象有个好处就是可以作为参数传递给其他函数,这种函数称为高阶函数1。在c++中不允许直接传递函数,虽然可以通过函数指针的形式来传递,但与函数对象相比会产生间接调用的开销,不利于编译器优化。

典型使用函数对象的场景是编写回调函数,它可以轻松地携带一些状态


c++支持操作符重载,而类可以储存私有状态,这两个特性组合起来便能定义一个函数对象

struct plus{
  int operator()(int x,int y)
  { return x + y;}
};
cout<<plus{}(2,3); //5

首先定义一个名为plus的类,其成员函数仅有一个operator()操作符,该函数实现了对两个int类型的数值进行相加的操作,而plus{}实例化了一个临时对象,因此可以使用成员函数调用动作plus{}(2,3),从而得到5

更进一步可以将该函数对象泛化,使其支持除int类型之外的其他数值类型,我们可以将该类型重构为模板类

template<typename T> struct plus{
  T operator ()(T x, T y){return x+y;}
};
cout<<plus<double>{}(2.2,3.3);//5.5

除了添加模板参数上的差异,实例化函数对象还需要显式指明类的模板参数plus<double>{ }如果需要为该函数对象添加状态,可以通过成员变量来储存状态,以下是实现任意数+N的函数对象

struct plusN {
  plus(int N):N(N){}//构造函数
  int operator()(int x)
  { return x+N; }
private:
  int N;
};
auto plus5=plusN{5};
cout<<plus5(2);//7
cout<<plus5(3);//8

上述代码添加了一个成员变量N用于存储加数,而这时的成员函数只需要接收一个参数,便能和已有的加数进行相加,在实例化对象的时候通过构造函数传递加数5,后续调用得到加5的结果

观察从plus到plusN的过程,其实函数的行为都一定,唯一不同的是后者的状态确定,更确切的是函数的两个参数中的一个参数确定了,也就是说其中一个参数被绑定了,程序员每次使用时无需传递被绑定的参数,只需要传递剩余的参数

为了避免每次都要为参数绑定动作实现一个对应的类与构造函数,标准库提供了bind来简化这个过程

using namespace std::placeholders;
auto plus5 = std::bind(plus<int>{},5,_1);

bind接收一个函数对象,这里需要对plus<int>{ }函数对象进行参数绑定,后续参数是待绑定的参数5,还有未提供的参数_1,这是一个来自于 std::placeholders名称空间的占位符,表示将来调用时才对这个参数进行绑定,bind最终生成一个只接受一个参数的函数对象plus5,当使用plus5(2)进行调用时,第二个参数2将被_1绑定,从而得到最终结果7

函数对象和普通类对象一样,可以赋给一个变量进行传递,配合bind等高阶函数,能够将一系列函数对象灵活组合成更高级的功能。考虑如下将所有大于4的数打印出来的代码

std::vector<int>nums = {5,3,2,5,6,1,7,4};j
std::copy_if(nums.begin(),nums.end(),std::ostream_iterator<int>(std::cout,","),std::bind(std::greater<int>{},_1,4));

copy_if接受一个输入迭代器区间和一个输出迭代器,接受一个单参的谓词函数2,对输入迭代器区间的每个元素进行谓词调用,若为真则把这个元素复制到输出迭代器上。greater<int>函数对象接受两个参数,判断这两个参数是否满足大于关系,通过bind将第二个参数绑定为4,得到一个只接受一个参数的谓词函数,从而实现了对每个元素是否大于4的判断

  1. 如排序函数 ↩︎
  2. 若有多个参数的话,第二个参数将被_2绑定,以此类推 ↩︎

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部