比较
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;
}
};
答案 0 :(得分:74)
至少有四个很好的理由:
关注点分离
在您的特定示例中,基于仿函数的方法具有将迭代逻辑与平均计算逻辑分离的优点。因此,您可以在其他情况下使用您的仿函数(考虑STL中的所有其他算法),并且可以使用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);
平均来自多个不同的数据集。
请注意,几乎所有接受仿函数的STL算法/容器都要求它们是“纯”谓词,即状态随时间没有可观察到的变化。 for_each
是这方面的特例(例如Effective Standard C++ Library - for_each vs. transform)。
<强>性能强>
编译器通常可以内联函数(毕竟,STL是一堆模板)。虽然理论上函数也是如此,但编译器通常不会内联函数指针。典型的例子是比较std::sort
与qsort
;假设比较谓词本身很简单,那么STL版本通常会快5-10倍。
<强>摘要强>
当然,可以使用传统函数和指针来模拟前三个,但是使用仿函数会变得更加简单。
答案 1 :(得分:9)
Functors的优点:
答案 2 :(得分:7)
std::for_each
很容易成为标准算法中反复无常且最不实用的。它只是一个很好的循环包装器。然而,即使它有优势。
考虑您的CalculateAverage
的第一个版本必须是什么样子。它将在迭代器上有一个循环,然后对每个元素进行处理。如果你错误地写了这个循环怎么办?哎呀;有编译器或运行时错误。第二个版本永远不会有这样的错误。是的,这不是很多代码,但为什么我们必须经常编写循环?为什么不一次?
现在,考虑真正的算法;实际上有效的。你想写std::sort
吗?还是std::find
?还是std::nth_element
?你甚至知道如何以最有效的方式实现它吗?您想要实施这些复杂算法多少次?
至于阅读的方便性,这是在旁观者的眼中。正如我所说,std::for_each
几乎不是算法的首选(尤其是C ++ 0x基于范围的语法)。但是,如果你在谈论真正的算法,它们是非常可读的; std::sort
对列表进行排序。像std::nth_element
这样的一些比较模糊的东西不会那么熟悉,但是你总是可以在你方便的C ++参考中查找它。
一旦你在C ++ 0x中使用Lambda,即使是std :: for_each也是完全可读的。
答案 3 :(得分:2)
在第一种方法中,迭代代码必须在所有想要对集合做某事的函数中重复。第二种方法隐藏了迭代的细节。
答案 4 :(得分:2)
•与功能不同,Functor可以具有状态。
这非常有趣,因为std :: binary_function,std :: less和std :: equal_to有一个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,然后更改成员变量。但最好的方法是什么?举个例子:
#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个解决方案,所以它的计数增加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
答案 5 :(得分:1)
OOP是关键字。
http://www.newty.de/fpt/functor.html:
4.1什么是Functors?
Functors是具有状态的函数。在C ++中,您可以将它们实现为具有一个或多个私有成员的类来存储状态,并使用重载的operator()来执行该函数。 Functors可以使用概念模板和多态来封装C和C ++函数指针。您可以构建一个指向任意类的成员函数的指针列表,并通过相同的接口调用它们,而不必担心它们的类或需要指向实例的指针。所有函数都必须具有相同的返回类型和调用参数。有时仿函数也称为闭包。您还可以使用仿函数来实现回调。
答案 6 :(得分:1)
您正在比较不同抽象级别的函数。
您可以将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
),在更复杂的情况下也是如此。你通常可以拥有一个简单的,通用的函子(或函数;普通的旧函数可以作为函子),并简单地将它与你需要的任何算法结合起来。