C++仿函数
400 Words | Read in about 2 Min | View times
Overview
C++中仿函数是一个能行使函数功能的类,仿函数的使用方法几乎和函数一样。那么仿函数和函数到底有什么区别?仿函数有哪些优点?本节内容将一一解答。
本系列文章将包括以下领域:
本章其他内容请见 《现代C++》
什么是仿函数?
想象一下这种场景,当我们有一个过滤函数Process
是用来过滤数组中大于10的元素,我们可以为“比较元素是否大于10”这个操作定义成一个函数F
,作为函数指针传入前面的过滤函数中。
如果此时要求把"10"也作为参数传入,可能就不那么美妙了。这个过滤函数Process
可能来自第三方库,我们并不能直接修改它的参数定义,那我们只好把"10"这个值定义成一个全局变量,再在函数F
中调用。
这种做法虽然可行,但是我们被迫要维护一个全局变量,容易出错。比如有一个同名的全局变量,那就会导致命名空间污染。总之,更优雅的方式就应运而生了,它就是仿函数。
仿函数,functor,又称函数对象,是指实现一个简单的类,并且重载了operator()
操作符,该类的实例化对象就可以行使函数的功能了。
以上例子,我们就可以定义个仿函数,在构造函数中将"10"作为参数传入函数对象内,由函数对象内部进行维护。同时重载一个operator()
操作符,在函数体中使用这个"10"即可。
仿函数与谓词的关系?
仿函数在不同语言的实现方式有所差别:
-
C语言使用函数指针来实现
-
C#使用delegate委托来实现
-
Java通过实现包含单个函数的接口来实现
-
C++通过实现一个重载
operator()
操作符的类来实现
在STL的实现中,使用了大量谓词(Predicate)来实现算法、容器、迭代器的抽象隔离。谓词和仿函数又是什么关系呢?
我们来看一个STL的算法for_each
的源码定义:
1/**
2 * @brief Apply a function to every element of a sequence.
3 * @ingroup non_mutating_algorithms
4 * @param __first An input iterator.
5 * @param __last An input iterator.
6 * @param __f A unary function object.
7 * @return @p __f
8 *
9 * Applies the function object @p __f to each element in the range
10 * @p [first,last). @p __f must not modify the order of the sequence.
11 * If @p __f has a return value it is ignored.
12*/
13template<typename _InputIterator, typename _Function>
14_GLIBCXX20_CONSTEXPR
15_Function
16for_each(_InputIterator __first, _InputIterator __last, _Function __f)
17{
18 // concept requirements
19 __glibcxx_function_requires(_InputIteratorConcept<_InputIterator>)
20 __glibcxx_requires_valid_range(__first, __last);
21 for (; __first != __last; ++__first)
22 __f(*__first);
23 return __f; // N.B. [alg.foreach] says std::move(f) but it's redundant.
24}
其中第三个参数__f
就是一个一元谓词。这个一元谓词既可以是一个普通的回调函数,也可以是一个仿函数即函数对象。
1//一元谓词——回调函数
2void showFunc(int item) {
3 std::cout << "调用回调函数:" << item <<< std::endl;
4}
5
6//一元谓词——仿函数
7class showFuncObj {
8public:
9 void operator()(int item) {
10 std::cout << "调用仿函数:" << item << std::endl;
11 }
12};
13
14int main() {
15 std::vector<int> vec {1,2,3,4,5,6,7};
16
17 for_each(vec.begin(), vec.end(), showFunc);
18
19 showFuncObj s;
20 for_each(vec.begin(), vec.end(), s);
21
22 return 0;
23}
所以,在这个例子中,仿函数一定是谓词,谓词不一定是仿函数,还可能是普通的回调函数。
仿函数的优点?
既然仿函数和函数的功能类型,那么仿函数到底有啥优点呢?
我们继续看上面的例子。
如果此时要求统计在for_each
过程中谓词被调用的次数,应该怎么办?
对于仿函数,我们只需要在showFuncObj
类中增加一个局部变量m_count
就可以很快扩展,而回调函数就没办法很好扩展了:
1//一元谓词——回调函数
2void showFunc(int item) {
3 std::cout << "调用回调函数:" << item <<< std::endl;
4}
5
6//一元谓词——仿函数
7class showFuncObj {
8public:
9 showFuncObj() : m_count(0) {}
10
11 void operator()(int item) {
12 std::cout << "调用仿函数:" << item << std::endl;
13 m_count++;
14 }
15
16 void count() { return m_count; }
17private:
18 m_count;
19};
20
21int main() {
22 std::vector<int> vec {1,2,3,4,5,6,7};
23
24 for_each(vec.begin(), vec.end(), showFunc);
25
26 showFuncObj s;
27 s = for_each(vec.begin(), vec.end(), s); //仿函数是按值传参
28 s.count();
29
30 return 0;
31}
仿函数的优点是能做到封装,功能扩展起来非常方便,而回调函数则没有办法。所以在STL的实现中,运用了大量仿函数来实现谓词。