STL_Function

《关于我转生变成史莱姆这档事》 -- 利姆露·特恩佩斯特

STL_Function

1 仿函数的概念

仿函数(functors)是早期的命名,C++标准规格定案后采用的名称是函数对象(function objects)。就现实意义而言,“函数对象”比较贴切,一种具有函数特质的对象。不过,就其行为而言,以及就中文用词的清晰漂亮与独特性而言,“仿函数”依次比较突出。

《STL源码剖析》 – P413

仿函数其实就是一个“行为类似函数”的对象,为了能够“行为类似函数”,其类别定义中必须自定义(或说改写、重载)function call 运算子(operator()),拥有这样的运算子后,我们就可以在仿函数的对象后面加上一堆小括号,以此调用仿函数所定义的 operator(),如下:

1
2
3
4
5
6
7
8
9
10
#include <functional>
#include <iostream>
using namespace std;
int main() {
greater<int> ig; // ig对象
cout << boolalpha << ig(4, 6) << endl; // 直接使用ig对象调用operator()
cout << ig.operator()(4, 6) << endl; // 通过ig对象显示调用operator()
cout << greater<int>()(6,4) << endl; // 通过产生临时对象调用operator()
return 0;
}

输出结果如下所示:

注意

  • 代码中 boolalpha 的作用:让输出流将 bool 解析成为 true 或者 false,当使用 boolalpha 后,以后的 bool 类型结果都将以 truefalse 形式输出,除非使用 noboolalpha 取消 boolalpha 流的格式标志。头文件 <iostream>
  • 在STL中,当需要传递仿函数对象的时候,通过采用最后一种方法,因为传递进去仅仅为了给容器内部成员赋值,所以没必要生成对象,产生临时对象即可。
  • STL 仿函数与 STL 算法之间的关系如图所示:
图 1 STL 仿函数与 STL 算法之间的关系

《STL源码剖析》 – P414

C++中 boolalpha 的用法

仿函数扮演一种“策略”角色,可以让 STL 算法有更灵活的演出,而更加灵活的关键,在于 STL 仿函数的可配接性 (adaptability)。STL 仿函数应该有能力被函数配接器function adapter)修饰,彼此像积木一样地串接,为了拥有配接能力,每个仿函数必须定义自己的相应型别,就像迭代器如果要融入整个 STL 大家庭,也必须依照规定定义自己的 5 个相应型别一样,这些相应型别是为了让配接器能够取出,获得 仿函数的某些信息。相应型别都只是一些 typedef,所有必要操作在编译期就全部完成了,对程序的执行效率没有任何影响,不带来额外负担。

仿函数的相应型别主要用来表现函数的参数型别和返回值,为了方便起见,<stl_function.h> 定义了两个 classes,分别代表一元仿函数二元仿函数,其中没有任何数据成员和成员函数,唯有一些型别定义,任何仿函数,只要依个人需求选择继承其中一个 class,便自动拥有了那些相应型别,也就自动拥有了配接能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ~ 一元运算符
template <class Arg, class Result>
struct unary_function {
using argument_type = Arg;
using result_type = Result;
};

// ! 二元操作结构定义
template <class Arg1, class Arg2, class Result>
struct binary_function {
using first_argument_type = Arg1;
using second_argument_type = Arg2;
using result_type = Result;
};

《STL源码剖析》 – P415 - P416


2. 仿函数的分类

2.1 算术类仿函数

STL 内建的“算术类仿函数”,支持加法、减法、乘法、除法、取余和否定运算,除了否定运算是一元运算,其他都是二元运算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 minus : public binary_function<T, T, T> {
T operator()(const T &x, const T &y) const { return x - y; }
};

template <class T>
struct multiplies : public binary_function<T, T, T> {
T operator()(const T &x, const T &y) const { return x * y; }
};

template <class T>
struct divides : public binary_function<T, T, T> {
T operator()(const T &x, const T &y) const { return x / y; }
};

template <class T>
struct modulus : public binary_function<T, T, T> {
T operator()(const T &x, const T &y) const { return x % y; }
};

template <class T> // 注意否定运算继承 unary_function (一元运算)
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
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>     // std::cout
#include <functional> // std::plus
using namespace std;
int main() {
plus<int> plusObj;
minus<int> minusObj;
multiplies<int> multipliesObj;
divides<int> dividesObj;
modulus<int> nodulusObj;
negate<int> negateObj;
cout << "以下运用上述对象,履行函数功能" << endl;

cout << plusObj(10, 5) << endl; // 15
cout << minusObj(10,5) << endl; // 5
cout << multipliesObj(10,5) << endl; // 50
cout << dividesObj(10,5) << endl; // 2
cout << modulusObj(10,5) << endl; // 0
cout << negateObj(10) << endl; //-10

cout << "以下直接以仿函数的临时对象履行函数功能" << endl;
cout << plus<int>()(10, 5) << endl; // 15
cout << minus<int>()(10,5) << endl; // 5
cout << multiplies<int>()(10,5) << endl; // 50
cout << divides<int>()(10,5) << endl; // 2
cout << modulus<int>()(10,5) << endl; // 0
cout << negate<int>()(10) << endl; //-10
return 0;
}

测试结果如下:

图 2 算术类仿函数测试结果

《STL源码剖析》 – P418 - P419

证同元素(identity element)

所谓“运算 op 的证同元素”,意思是数值 A 若与该元素做 op 运算,会得到 A 自己,加法的证同元素为 0,因为任何元素加上 0 仍然是自己;乘法的证同元素是 1,因为任何元素乘以 1,因为任何元素乘以 1仍然是自己。

1
2
3
4
5
6
7
8
9
// 证同元素(并非标准STL所要求)
template <class T>
inline T identity_element(plus<T>) {
return T(0);
}
template <class T>
inline T identity_element(multiplies<T>) {
return T(1);
}

《STL源码剖析》 – P420


2.2 关系运算类仿函数

STL 内建的“关系运算类仿函数”,支持等于、不等于、大于、大于等于、小于和小于等于六种运算,每一个都是二元运算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 not_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; }
};

template <class T>
struct less : public binary_function<T, T, bool> {
bool operator()(const T &x, const T &y) const { return x < y; }
};

template <class T>
struct greater_equal : public binary_function<T, T, bool> {
bool operator()(const T &x, const T &y) const { return x >= y; }
};

template <class T>
struct less_equal : 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
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>     // std::cout
#include <algorithm> // std::sort
#include <vector> // std::vector
#include <functional> // std::less<T>
using namespace std;

bool myfunction (int i,int j) { return (i < j); }

int main () {
int myints[] = {32,71,12,45,26,80,53,33};
std::vector<int> myvector (myints, myints+8);

cout << "using default comparison (operator <)" << endl;
std::sort (myvector.begin(), myvector.begin()+4);
for (const auto& v : myvector) cout << v << ' ';
cout << endl;
cout << "-------------------------------"<< endl;
// (12 32 45 71) 26 80 53 33

cout << "using function as comp" << endl;
std::sort (myvector.begin()+4, myvector.end(), myfunction);
for (const auto& v : myvector) cout << v << ' ';
cout << endl;
cout << "-------------------------------"<< endl;
// 12 32 45 71 (26 33 53 80)

cout << "using object as comp" << endl;
std::sort (myvector.begin(), myvector.end(), std::less<int>());
for (const auto& v : myvector) cout << v << ' ';
cout << endl;
// (12 26 32 33 45 53 71 80)

return 0;
}

测试结果如下:

例子使用三种方式配合排序算法实现排序:

  • 使用默认的 compoperator <
  • 使用用户自定义的 compmyfunction
  • 使用仿函数 : less<int>()

《STL源码剖析》 – P420 - P422

STL之仿函数实现详解,<-- 例子来源


2.3 逻辑运算类仿函数

STL 内建的“ 逻辑运算类仿函数”,支持与或非三种运算,与或为二元运算,非为一元运算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
20
#include <iostream>
#include <functional>
using namespace std;
int main() {
logical_and<int> andObj;
logical_or<int> orObj;
logical_not<int> notObj;
cout << "以下运用上述对象,履行函数功能" << endl;
cout << boolalpha;
cout << andObj(true, true) << endl;
cout << orObj(true, false) << endl;
cout << notObj(true) << endl;

cout << "以下直接以仿函数的临时对象履行函数功能" << endl;
cout << logical_and<int>()(true, true) << endl;
cout << logical_or<int>()(true, false) << endl;
cout << logical_not<int>()(true) << endl;

return 0;
}

测试结果如下:

《STL源码剖析》 – P422 - P423 <-- 例子来源


2.4 证同、选择、投射

这一节介绍的仿函数,都只是将其参数原封不动地传回,其中某些仿函数对传回的参数有可以的选择,或是刻意忽略,之所以不在 STL 或其他泛型程序设计过程中直接使用原本极其简单的 identity、project、select 等操作,而在要划分一层出来,全是为了间接性 – 间接性是抽象化的重要工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// @ 证同仿函数,主要用于 RB tree 或者 hashmap 里面 key = value 情况
template <class T>
struct identity : public unary_function<T, T> {
const T &operator()(const T &x) const { return x; }
}; // @ identity<T>()(x)

// @ 选择仿函数,主要用与 RB treee 和 hashmap,从 pair 中取出 key
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;
}
}; // @ identity<pair>()(x)

// @ 选择仿函数,主要用与 RB treee 和 hashmap,从 pair 中取出 value
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;
}
};

// @ 投射函数,输入 x 和 y 返回 x
template <class Arg1, class Arg2>
struct project1st : public binary_function<Arg1, Arg2, Arg1> {
Arg1 operator()(const Arg1 &x, const Arg2 &) const { return x; }
};

// @ 投射函数,输入 x 和 y 返回 y
template <class Arg1, class Arg2>
struct Project2nd : public binary_function<Arg1, Arg2, Arg2> {
Arg2 operator()(const Arg1 &, const Arg2 &y) const { return y; }
};

《STL源码剖析》 – P423 - P424


函数配接器内容见 STL_Adapter


3. 参考资料

《STL源码剖析》

C++中 boolalpha 的用法

STL之仿函数实现详解

STL_Iterator

STL_Adapter

MiniSTL / Function 部分



----------- 本文结束 -----------




0%