仿函数(function object,函数对象)
目录
引子
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<int>() 声明了一个临时对象,然后传入参数(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<T>
- 减法:minus<T>
- 乘法:multiplies<T>
- 除法:divides<T>
- 模取:modulus<T>
- 否定:negate<T>
关系运算类仿函数
- 等于:equal_to<T>
- 不等于:not_equal_to<T>
- 大于:greater<T>
- 大等于:greater_equal<T>
- 小于:less<T>
- 小等于:less_equal<T>
逻辑运算类仿函数
- 逻辑运算And:logical_and<T>
- 逻辑运算Or:logical_or<T>
- 逻辑运算Not:logical_not<T>
一般而言,很少有在单纯情况下使用功能极其简单的仿函数的。STL的仿函数主要搭配STL算法。例如对vector<int>元素进行递减排序:
1 2 3 |
//vector<int> vc; //vc.push_back(4)...... sort(vc.begin(),vc.end(),greater<int>()); |
区别与优势
逻辑和算法分离
这使得仿函数可以用于不同的场合,算法也可以和不同逻辑的仿函数搭配使用。参见STL的for_each;
有状态
仿函数是一个对象。它是可以带有状态的。这是指针函数无法实现的。
高性能
仿函数的实现可以是内联的。使得编译器知道算法调用了哪些代码。而指针函数是在运行时才能确定指针指向的函数。