仿函数
仿函数是可调用对象,用于参数化 STL 算法,支持算术、关系、逻辑及投射操作,实现抽象化与灵活复用。
仿函数
STL 中的仿函数(Functors / Function Objects)
名称来源
- Functors(仿函数):早期命名,中文翻译独特、形象。
- Function Objects(函数对象):C++ 标准采用的正式名称,更贴切其“对象具有函数特质”的本质。
概念
仿函数是一个行为类似函数的对象。
本质上就是一个类(class),其中重载了函数调用运算符
operator()
。调用时可以写作:
1 2 3
greater<int> ig; ig(4, 6); // 调用 ig.operator()(4,6) greater<int>()(6, 4); // 使用临时对象调用
作用
在 STL 算法中,仿函数用于作为 “策略参数” 传入算法。
STL 常提供两种算法版本:
- 默认版本:采用常见操作(如
operator+
,operator<
)。 - 泛化版本:允许用户传入仿函数,自定义行为。
- 例:
accumulate()
默认执行加法,但可以传入仿函数定义其他累积操作。sort()
默认使用<
比较,但可以传入greater<>
或用户自定义的比较仿函数。
- 默认版本:采用常见操作(如
与函数指针的区别
- 函数指针 也能传递“操作”,但存在局限:
- 不能与 STL 其它组件(如 适配器 adapters)良好结合。
- 可扩展性、抽象性较差。
- 仿函数对象:
- 是类对象,可以携带状态。
- 可与 STL 的 适配器 配合,形成更灵活的抽象。
使用语法
两种常见用法:
具名对象:
1 2
greater<int> ig; cout << boolalpha << ig(4, 6); // false
临时对象(主流用法):
1
cout << greater<int>()(6, 4); // true
分类
- 按操作数个数:
- 一元仿函数(Unary Functor)
- 二元仿函数(Binary Functor)
- 按功能:
- 算术运算(Arithmetic)
- 关系运算(Relational)
- 逻辑运算(Logical)
头文件
使用 STL 内建仿函数需包含:
1
#include <functional>
在 SGI STL 中,具体定义位于
<stl_function.h>
。
仿函数的可配接性 (Adaptability)
仿函数在STL中虽然简单,却能作为“策略”让算法表现出不同的行为。为了能与函数配接器组合使用,仿函数必须具备可配接性。这就要求仿函数定义一些相应型别,用来表示参数类型和返回值类型。相应型别只是通过 typedef
在编译期完成,不影响运行时效率。SGI STL 在 <stl_function.h>
中提供了 unary_function<Arg, Result>
和 binary_function<Arg1, Arg2, Result>
两个基类,它们没有数据成员或函数,只有必要的型别定义。任何自定义仿函数只要继承这两个基类之一,就能自动获得所需的相应型别,从而具备可配接性。
unary_function
unary_function
用来表示一元仿函数的参数型别和返回值型别。定义如下:
1
2
3
4
5
6
// 每一个 Adaptable Unary Function 都应该继承此类
template <class Arg, class Result>
struct unary_function {
typedef Arg argument_type;
typedef Result result_type;
};
一旦某个仿函数继承了 unary_function
,用户就可以通过 argument_type
和 result_type
取得其参数与返回值型别。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 继承 unary_function 的一元仿函数
template <class T>
struct negate : public unary_function<T, T> {
T operator()(const T& x) const { return -x; }
};
// 配接器:对某个仿函数取逻辑负值
template <class Predicate>
class unary_negate {
public:
bool operator()(const typename Predicate::argument_type& x) const {
// ...
}
};
binary_function
binary_function
用来表示二元仿函数的第一参数型别、第二参数型别和返回值型别。定义如下:
1
2
3
4
5
6
7
// 每一个 Adaptable Binary Function 都应该继承此类
template <class Arg1, class Arg2, class Result>
struct binary_function {
typedef Arg1 first_argument_type;
typedef Arg2 second_argument_type;
typedef Result result_type;
};
一旦某个仿函数继承了 binary_function
,用户就可以通过 first_argument_type
、second_argument_type
和 result_type
取得其相应型别。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 继承 binary_function 的二元仿函数
template <class T>
struct plus : public binary_function<T, T, T> {
T operator()(const T& x, const T& y) const { return x + y; }
};
// 配接器:将二元仿函数转化为一元仿函数
template <class Operation>
class binder1st {
protected:
Operation op;
typename Operation::first_argument_type value;
public:
typename Operation::result_type
operator()(const typename Operation::second_argument_type& x) const {
// ...
}
};
算术类 (Arithmetic) 仿函数
STL 内建了 6 种算术类仿函数,支持加、减、乘、除、取模和取负运算。其中只有取负是 一元运算,其余都是 二元运算:
plus<T>
:加法minus<T>
:减法multiplies<T>
:乘法divides<T>
:除法modulus<T>
:取模negate<T>
:取负
这些仿函数都继承自 unary_function
或 binary_function
,提供了参数和返回值的相应型别定义。例如:
1
2
3
4
5
6
7
8
9
template <class T>
struct plus : public binary_function<T, T, T> {
T operator()(const T& x, const T& y) const { return x + y; }
};
template <class T>
struct negate : public unary_function<T, T> {
T operator()(const T& x) const { return -x; }
};
使用示例
仿函数对象的使用与普通函数完全一致,可以通过 具名对象 或 临时对象 调用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <functional>
using namespace std;
int main() {
plus<int> plusobj;
minus<int> minusobj;
cout << plusobj(3, 5) << endl; // 8
cout << minusobj(3, 5) << endl; // -2
// 临时对象调用
cout << plus<int>()(3, 5) << endl; // 8
cout << minus<int>()(3, 5) << endl; // -2
}
在实际应用中,算术仿函数主要与 STL 算法 搭配。例如:
1
2
// 用 multiplies<int>() 计算所有元素的连乘积
accumulate(iv.begin(), iv.end(), 1, multiplies<int>());
证同元素 (Identity Element)
所谓某个运算 p
的 证同元素,是指对任意数值 A,满足 A ⊕ e = A
的元素 e。
- 加法的证同元素是 0
- 乘法的证同元素是 1
SGI STL 还提供了 identity_element()
辅助函数(非标准),用于快速获取:
1
2
3
4
5
template <class T>
inline T identity_element(plus<T>) { return T(0); }
template <class T>
inline T identity_element(multiplies<T>) { return T(1); }
其中乘法的证同元素 1 在 <stl_numeric.h>
的 power()
算法中会被实际使用。
关系运算类 (Relational) 仿函数
STL 内建了 6 种关系运算类仿函数,支持常见的比较运算。它们都是 二元运算,返回值为 bool
类型。
equal_to<T>
:等于not_equal_to<T>
:不等于greater<T>
:大于greater_equal<T>
:大于等于less<T>
:小于less_equal<T>
:小于等于
这些仿函数同样继承自 binary_function
,提供了参数与返回值型别定义。例如:
1
2
3
4
5
6
7
8
9
template <class T>
struct equal_to : public binary_function<T, T, bool> {
bool operator()(const T& x, const T& y) const { return x == y; }
};
template <class T>
struct greater : public binary_function<T, T, bool> {
bool operator()(const T& x, const T& y) const { return x > y; }
};
使用示例
仿函数对象的用法与一般函数相同,可以使用 具名对象 或 临时对象 调用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <functional>
using namespace std;
int main() {
equal_to<int> equal_to_obj;
greater<int> greater_obj;
cout << equal_to_obj(3, 5) << endl; // 0
cout << greater_obj(3, 5) << endl; // 0
// 临时对象调用
cout << equal_to<int>()(3, 5) << endl; // 0
cout << greater<int>()(3, 5) << endl; // 0
}
搭配 STL 算法
在实际开发中,这些关系运算仿函数通常与 STL 算法结合使用。例如:
1
2
3
4
5
// 按递增顺序排序
sort(iv.begin(), iv.end(), less<int>());
// 按递减顺序排序
sort(iv.begin(), iv.end(), greater<int>());
通过传入不同的关系运算仿函数,可以轻松改变排序或查找等算法的行为。
逻辑运算类 (Logical) 仿函数
STL 内建了三种逻辑运算类仿函数,分别对应逻辑运算中的 And、Or、Not。其中 And 与 Or 为二元运算,Not 为一元运算。
logical_and<T>
:逻辑与logical_or<T>
:逻辑或logical_not<T>
:逻辑非
其定义大致如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 逻辑与
template <class T>
struct logical_and : public binary_function<T, T, bool> {
bool operator()(const T& x, const T& y) const { return x && y; }
};
// 逻辑或
template <class T>
struct logical_or : public binary_function<T, T, bool> {
bool operator()(const T& x, const T& y) const { return x || y; }
};
// 逻辑非
template <class T>
struct logical_not : public unary_function<T, bool> {
bool operator()(const T& x) const { return !x; }
};
这些仿函数对象的用法和普通函数完全相同,可以通过实体对象或临时对象来调用。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <functional>
using namespace std;
int main() {
// 定义仿函数对象
logical_and<int> and_obj;
logical_or<int> or_obj;
logical_not<int> not_obj;
cout << and_obj(true, true) << endl; // 1
cout << or_obj(true, false) << endl; // 1
cout << not_obj(true) << endl; // 0
// 使用临时对象调用
cout << logical_and<int>()(true, true) << endl; // 1
cout << logical_or<int>()(true, false) << endl; // 1
cout << logical_not<int>()(true) << endl; // 0
}
通常不会在如此简单的逻辑场景中单独使用这些仿函数,它们的主要用途是 搭配 STL 算法,例如在 transform
、count_if
、remove_if
等算法中作为谓词(predicate)传入,从而实现逻辑运算与条件判断的功能。
证同 (identity)、选择 (select)、投射 (project)
这一类仿函数(identity、select、project)都只是将输入参数原封不动传回,或者有选择性地返回其中一部分。虽然这些操作本身极其简单,但在泛型编程和 STL 内部实现中,为了抽象和间接性,通常会专门定义出这些仿函数。
C++ 标准并没有规定这类仿函数,但在 SGI STL 等实现中,它们常常被用作底层工具。
identity(证同函数)
- 任何数值通过此仿函数后不会有任何改变。
- 常用于 set 的底层 RB-tree,因为 set 的键值就是元素自身,所以
KeyOfValue
选择器直接用identity
。
1
2
3
4
template <class T>
struct identity : public unary_function<T, T> {
const T& operator()(const T& x) const { return x; }
};
使用示例:set<int>
内部用 identity<int>
来告诉 RB-tree,键值就是元素本身,不需要另外取子成员。
1
2
3
identity<int> id;
int x = 5;
cout << id(x); // 输出 5
select1st(选择第一元素)
- 接收一个
pair
,返回其 first 元素。 - 常用于 map 的底层 RB-tree,因为 map 的键值就是
pair
的第一元素。
1
2
3
4
5
6
template <class Pair>
struct select1st : public unary_function<Pair, typename Pair::first_type> {
const typename Pair::first_type& operator()(const Pair& x) const {
return x.first;
}
};
select2nd(选择第二元素)
- 接收一个
pair
,返回其 second 元素。 - SGI STL 并未在内部使用,但在一些场景下可能派上用场。
- 用途:
map
容器内部,键是pair.first
,值是pair.second
。用 select1st/select2nd 可以让算法或容器方便地取得 key 或 value,而不用手动写x.first
/x.second
。
1
2
3
4
5
6
template <class Pair>
struct select2nd : public unary_function<Pair, typename Pair::second_type> {
const typename Pair::second_type& operator()(const Pair& x) const {
return x.second;
}
};
使用示例:
1
2
3
4
5
6
pair<int, string> p = {1, "hello"};
select1st<pair<int,string>> s1;
select2nd<pair<int,string>> s2;
cout << s1(p); // 输出 1
cout << s2(p); // 输出 "hello"
project1st(投射第一参数)
- 接收两个参数,返回第一个,忽略第二个。
1
2
3
4
template <class Arg1, class Arg2>
struct project1st : public binary_function<Arg1, Arg2, Arg1> {
Arg1 operator()(const Arg1& x, const Arg2&) const { return x; }
};
project2nd(投射第二参数)
- 接收两个参数,返回第二个,忽略第一个。
- 用途:有时候算法需要一个二元函数,但我们只想用其中一个参数,比如做排序或筛选时忽略某个值。
1
2
3
4
template <class Arg1, class Arg2>
struct project2nd : public binary_function<Arg1, Arg2, Arg2> {
Arg2 operator()(const Arg1&, const Arg2& y) const { return y; }
};
使用示例:
1
2
3
4
5
project1st<int,int> p1;
project2nd<int,int> p2;
cout << p1(10, 20); // 输出 10,忽略 20
cout << p2(10, 20); // 输出 20,忽略 10
这些仿函数的意义并不在于“功能强大”,而是提供统一的抽象接口,让底层容器和算法在实现时能通过参数化来选择需要的“取值方式”。这就是泛型编程中所谓的 间接性与抽象化。
这些仿函数的作用本质是 告诉算法或容器应该“取哪部分数据”或者“怎么取数据”,而不是自己去写重复的逻辑。