仿函数(function object,函数对象)

引子

前一篇文章讲到了C++11的新特性lambda表达式

lambda表达式的本质是什么呢?为探究这个问题,我们将一段写有lambda表达式的代码反编译,可能会发现一些秘密.

首先写段程序:

1
2
3
4
5
6
7
//#include...
auto flambda = [](int x,int y)->int{return x + y;};
int main()
{
cout<<flambda(3,2)<<endl;
return 0;
}

在调试该代码时我们打开汇编视图,发现如下代码:

1
2
3
callq  0x100000d80 <$_0::operator()(int, int) const>
mov 0x2c4(%rip),%rdi # 0x100001010
mov %eax,%esi

其中 <$_0::operator()(int, int) const> 即对应原代码中的lambda表达式。这里一串重载了"()"(operator())操作符的代码,即本文将要了解的仿函数

Note: 此处的 const ,也应证了前文 mutable 可变范围 中提到的,lambda的调用运算符为"const-by-value"的

定义

仿函数(finctor)是一种早期的叫法,在C++11中,标准的叫法为函数对象(function object)。仿函数是一种行为类似函数、具有函数特性的对象。

仿函数是一个对象,而不是函数。仿函数的类通过重载函数操作符"()"(operator())来实现函数调用的特性。

通过下面的例子来简单说明如何使用仿函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <functional>
//#include...

class myGreater
{
public:
bool operator()(int x,int y) //重载调用操作函数
{return x>y;}
};

int main()
{
greater<int> gt; //lineA
cout<<boolalpha<<gt(4,3)<<endl; //lineB
cout<<greater<int>()(3,4)<<endl; //lineC
cout<<myGreater()(5,3)<<endl; //lineD
return 0;
}
//OUTPUT:
//true
//false
//true

这个例子中,lineA声明了一个对象。这是一个函数对象,其类(结构)定义在functional文件中,是STL内置的模板类。该类用于比较两个参数的大小。

lineB则像函数一样直接给该对象传入两个参数。

lineC是另一种写法。greater() 声明了一个临时对象,然后传入参数(3,4)调用该临时对象。这种写法往往比lineB更加简洁。在STL源码里大量使用了这种写法。

lineD则是使用自定义的函数对象。

Note:lineB中使用了boolalpha,是iostream提供的一个操作符,这将使此后的标准输出中,将bool值输出为"true"或"false",而不是1/0。于其相反的操作为noboolalpha.

STL中的仿函数

仿函数是STL六大组件之一。它一般与STL算法配合使用。

1
2
3
4
5
6
algorithm(first,last,...,functorObj)
{
...
functorObj(...); //STL内建仿函数或用户自定义仿函数
...
}

STL内建的仿函数,若以操作数个数据划分,可以分为一元和二元仿函数;若以功能划分,则可分为算术运算(arithmectic)、关系运算(Rational)、逻辑运算(Logical)三大类。

算术类仿函数
  • 加法:plus

  • 减法:minus

  • 乘法:multiplies

  • 除法:divides

  • 模取:modulus

  • 否定:negate

关系运算类仿函数
  • 等于:equal_to

  • 不等于:not_equal_to

  • 大于:greater

  • 大等于:greater_equal

  • 小于:less

  • 小等于:less_equal

逻辑运算类仿函数
  • 逻辑运算And:logical_and

  • 逻辑运算Or:logical_or

  • 逻辑运算Not:logical_not

一般而言,很少有在单纯情况下使用功能极其简单的仿函数的。STL的仿函数主要搭配STL算法。例如对vector元素进行递减排序:

1
2
3
//vector<int> vc;
//vc.push_back(4)......
sort(vc.begin(),vc.end(),greater<int>());

区别与优势

逻辑和算法分离

这使得仿函数可以用于不同的场合,算法也可以和不同逻辑的仿函数搭配使用。参见STL的for_each;

有状态

仿函数是一个对象。它是可以带有状态的。这是指针函数无法实现的。

高性能

仿函数的实现可以是内联的。使得编译器知道算法调用了哪些代码。而指针函数是在运行时才能确定指针指向的函数。