为什么要使用函子而不是函数?
比较
double average = CalculateAverage(values.begin(), values.end());
同
double average = std::for_each(values.begin(), values.end(), CalculateAverage());
在函数中使用函子有什么好处? 是不是第一个更容易阅读(甚至在添加实现之前)?
假设仿函数是这样定义的:
class CalculateAverage { private: std::size_t num; double sum; public: CalculateAverage() : num (0) , sum (0) { } void operator () (double elem) { num++; sum += elem; } operator double() const { return sum / num; } };
至less有四个好理由:
关注点分离
在你的具体例子中,基于仿函数的方法具有将迭代逻辑从平均值计算逻辑中分离出来的优点。 所以你可以在其他情况下使用你的函数(想想STL中的所有其他algorithm),你可以使用其他函数for_each
。
参数化
您可以更轻松地对函数进行参数化。 因此,例如,您可以使用一个CalculateAverageOfPowers
函子,它可以获取数据的平方或立方体等的平均值,从而可以这样写:
class CalculateAverageOfPowers { public: CalculateAverageOfPowers(float p) : acc(0), n(0), p(p) {} void operator() (float x) { acc += pow(x, p); n++; } float getAverage() const { return acc / n; } private: float acc; int n; float p; };
你当然可以用传统的函数做同样的事情,但是使用函数指针很困难,因为它与CalculateAverage
有不同的原型。
有状态
作为函数可以是有状态的,你可以做这样的事情:
CalculateAverage avg; avg = std::for_each(dataA.begin(), dataA.end(), avg); avg = std::for_each(dataB.begin(), dataB.end(), avg); avg = std::for_each(dataC.begin(), dataC.end(), avg);
以平均数量不同的数据集。
请注意,几乎所有接受函子的STLalgorithm/容器都要求它们是“纯粹的”谓词,即在一段时间内没有可观察的状态变化。 for_each
在这方面是一个特殊情况(请参阅有效的标准C ++库 – for_each与变换 )。
性能
函数经常可以被编译器内联(STL毕竟是一堆模板)。 虽然理论上对函数也是如此,编译器通常不会内联函数指针。 规范的例子是比较std::sort
和qsort
; 假设比较谓词本身很简单,STL版本通常快5-10倍。
概要
当然,可以用传统的函数和指针来模拟前三个,但是用函数来变得更简单。
玩家的优势:
- 不像函数Functor可以有状态。
- 与函数相比,Functor适合OOP范例。
- Functor通常可以像函数指针那样内联
- Functor不需要vtable和运行时调度,因此在大多数情况下效率更高。
std::for_each
很容易就是标准algorithm中最反复无常和最不实用的。 这只是一个循环的包装。 但是,即使它有优势。
考虑一下你的CalculateAverage
的第一个版本是什么样的。 它将遍历迭代器,然后对每个元素进行操作。 如果你写错了这个循环会发生什么? 哎呀; 有一个编译器或运行时错误。 第二个版本不能有这样的错误。 是的,这不是很多代码,但是为什么我们必须经常编写循环呢? 为什么不只是一次?
现在,考虑真正的algorithm; 那些实际上工作的人。 你想写std::sort
吗? 或者std::find
? 或std::nth_element
? 你甚至知道如何以最有效的方式实现它? 你想要实现这些复杂algorithm多less次?
至于阅读的便利,这是在旁观者的眼中。 正如我所说的, std::for_each
几乎不是algorithm的首选(尤其是C ++ 0x基于范围的语法)。 但如果你正在谈论真正的algorithm,他们是非常可读的; std::sort
sorting列表。 像std::nth_element
这些更晦涩的东西不会那么熟悉,但是您可以随时在方便的C ++参考中查找它。
一旦你在C ++ 0x中使用Lambda,甚至std :: for_each也是完全可读的。
在第一种方法中,迭代代码必须在所有想要对集合进行操作的函数中进行复制。 第二种方法隐藏了迭代的细节。
OOP是关键字在这里。
http://www.newty.de/fpt/functor.html :
4.1什么是Functor?
函子是一个状态的函数。 在C ++中,您可以将它们实现为具有一个或多个私有成员的类来存储状态,并使用重载的operator()来执行该函数。 函子可以封装使用概念模板和多态的C和C ++函数指针。 你可以build立一个指向任意类的成员函数的指针列表,并通过相同的接口调用它们,而不用担心它们的类或需要指向实例的指针。 所有的函数都必须具有相同的返回types和调用参数。 有时函子也被称为闭包。 您也可以使用函数来实现callback。
您正在比较不同抽象层次上的function。
您可以将CalculateAverage(begin, end)
为:
template<typename Iter> double CalculateAverage(Iter begin, Iter end) { return std::accumulate(begin, end, 0.0, std::plus<double>) / std::distance(begin, end) }
或者你可以用for循环来做
template<typename Iter> double CalculateAverage(Iter begin, Iter end) { double sum = 0; int count = 0; for(; begin != end; ++begin) { sum += *begin; ++count; } return sum / count; }
前者要求你知道更多的东西,但是一旦你了解了他们,就会变得更简单,出错的可能性更小。
它也只使用两个通用组件( std::accumulate
和std::plus
),在更复杂的情况下也是如此。 通常可以有一个简单的通用仿函数(或函数,普通的旧函数可以作为仿函数),并将其与任何你需要的algorithm简单地结合起来。
•与函数Functor可以有状态不同。
这是非常有趣的,因为std :: binary_function,std :: less和std :: equal_to有一个operator()的模板,它是const的。 但是如果你想打印一个带有当前对象的调用消息的话,你会怎么做呢?
这里是std :: equal_to的模板:
struct equal_to : public binary_function<_Tp, _Tp, bool> { bool operator()(const _Tp& __x, const _Tp& __y) const { return __x == __y; } };
我可以想到3种方式允许operator()是const,但是改变一个成员variables。 但是最好的办法是什么? 以这个例子:
#include <iostream> #include <string> #include <algorithm> #include <functional> #include <cassert> // assert() MACRO // functor for comparing two integer's, the quotient when integer division by 10. // So 50..59 are same, and 60..69 are same. // Used by std::sort() struct lessThanByTen: public std::less<int> { private: // data members int count; // nr of times operator() was called public: // default CTOR sets count to 0 lessThanByTen() : count(0) { } // @override the bool operator() in std::less<int> which simply compares two integers bool operator() ( const int& arg1, const int& arg2) const { // this won't compile, because a const method cannot change a member variable (count) // ++count; // Solution 1. this trick allows the const method to change a member variable ++(*(int*)&count); // Solution 2. this trick also fools the compilers, but is a lot uglier to decipher ++(*(const_cast<int*>(&count))); // Solution 3. a third way to do same thing: { // first, stack copy gets bumped count member variable int incCount = count+1; const int *iptr = &count; // this is now the same as ++count *(const_cast<int*>(iptr)) = incCount; } std::cout << "DEBUG: operator() called " << count << " times.\n"; return (arg1/10) < (arg2/10); } }; void test1(); void printArray( const std::string msg, const int nums[], const size_t ASIZE); int main() { test1(); return 0; } void test1() { // unsorted numbers int inums[] = {33, 20, 10, 21, 30, 31, 32, 22, }; printArray( "BEFORE SORT", inums, 8 ); // sort by quotient of integer division by 10 std::sort( inums, inums+8, lessThanByTen() ); printArray( "AFTER SORT", inums, 8 ); } //! @param msg can be "this is a const string" or a std::string because of implicit string(const char *) conversion. //! print "msg: 1,2,3,...N", where 1..8 are numbers in nums[] array void printArray( const std::string msg, const int nums[], const size_t ASIZE) { std::cout << msg << ": "; for (size_t inx = 0; inx < ASIZE; ++inx) { if (inx > 0) std::cout << ","; std::cout << nums[inx]; } std::cout << "\n"; }
因为所有3种解决scheme都被编译进来,所以它的计数递增了3.下面是输出:
gcc -g -c Main9.cpp gcc -g Main9.o -o Main9 -lstdc++ ./Main9 BEFORE SORT: 33,20,10,21,30,31,32,22 DEBUG: operator() called 3 times. DEBUG: operator() called 6 times. DEBUG: operator() called 9 times. DEBUG: operator() called 12 times. DEBUG: operator() called 15 times. DEBUG: operator() called 12 times. DEBUG: operator() called 15 times. DEBUG: operator() called 15 times. DEBUG: operator() called 18 times. DEBUG: operator() called 18 times. DEBUG: operator() called 21 times. DEBUG: operator() called 21 times. DEBUG: operator() called 24 times. DEBUG: operator() called 27 times. DEBUG: operator() called 30 times. DEBUG: operator() called 33 times. DEBUG: operator() called 36 times. AFTER SORT: 10,20,21,22,33,30,31,32