仿函数functors另名函数对象functions objects。在之前的算法介绍中,我们已经用到了仿函数。此章,我们对仿函数做一个更详细的介绍。
仿函数概览
这一节所探索的东西,在STL历史上有两个不同的名称。仿函数(functors)是早期的命名,C++标准规格定案后所采用的新名称是函数对象(function objects)。
就实现意义而言,“函数对象”比较贴切:一种具有函数特质的对象。不过就其行为而言,以及就中文用词的清晰漂亮与独特性而言,“仿函数”一词比较突出。因此,本书绝大部分时候采用“仿函数”一词。这种东西在调用者可以像函数一样被调用,在被调用者则以对象所定义的function call operator扮演函数的实质角色。
仿函数的作用主要是什么?从STL所提供的各种算法,往往存在两个版本,其一版本使用了默认操作符,另一个版本可以传入仿函数。例如sort,默认的比较操作符为operator<,另一个版本允许用户指定任何操作,务求排序后的两两相邻元素都能令操作结果为true。亦即,要将某种操作当做算法的参数,唯一办法就是先将该“操作”设计为一个函数,再将函数指针当做算法的一个参数;或者将该操作设计为一个所谓的仿函数(事实上为class),再以该仿函数产生一个对象,并以此对象作为算法的一个参数。
根据以上陈述,既然函数指针可以达到“将数组操作当做算法的参数”,那又何必有所谓的仿函数呢?原因在于函数指针毕竟不能满足STL对抽象性的要求,也不能满足软件积木的要求----函数指针无法和STL其它组件搭配,产生更灵活的变化。
就实现观点而言,仿函数其实就是一个“行为类似函数”的对象。为了能够“行为类似函数”,其类别定义中必须自定义(或说改写、重载)function call运算子(operator())。拥有这样的算子后,我们就可以再仿函数的对象后面加上一对小括号,以此调用仿函数所定义的operator(),示例如下:
#include <functional>
#include <iostream>
using namespace std;
int main() {
greater<int> ig;
cout << boolalpha << ig(4, 6) << endl; // false
cout << greater<int>()(6, 4) << endl; // true
return 0;
}
其中第一种用法比较为大家所熟悉,greate<int> ig的意思是产生一个名为ig的对象,ig(4,6)则是调用其operator(),并给予两个参数4,6。第二种用法中的greater<int>()意思是产生一个临时(无名的)对象,之后的(6,4)才是指定的两个参数6,4.
上述第二种语法在一般情况下不常见,但是对仿函数而言,确实主流用法。
STL仿函数的分类,若以操作数(operand)的个数划分,可分为一元和二元仿函数,若以功能划分,可分为算术运算,关系运算,逻辑运算三大类。任何应用程序欲使用STL内建的仿函数,必须包含(include)头文件<functional>,SGI将它们实际定义于<stl_function.h>中。
可适配(Adaptable)的关键
在STL六大组件中,仿函数可以说是体积最小,观念最简单、实现最容易的一个。但是小兵也能立大功----它扮演一种“策略”角色,可以让STL算法有更灵活的演出。而更加灵活的关键,在于STL仿函数的可适配性(adaptablility)。
是的,STL仿函数应该有能力被函数适配器(function adapterÿ