为什么使用函子而不是函数?
比较
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;
}
};
Compare
double average = CalculateAverage(values.begin(), values.end());
with
double average = std::for_each(values.begin(), values.end(), CalculateAverage());
What are the benefits of using a functor over a function? Isn't the first a lot easier to read (even before the implementation is added)?
Assume the functor is defined like this:
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;
}
};
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
至少有四个充分的理由:
关注点分离
在您的特定示例中,基于函子的方法具有将迭代逻辑与平均计算逻辑分离的优点。因此,您可以在其他情况下使用您的函子(想想 STL 中的所有其他算法),并且您可以将其他函子与
for_each
一起使用。参数化
您可以更轻松地参数化函子。例如,您可以有一个 CalculateAverageOfPowers 函子,它取数据的平方或立方等的平均值,可以这样写:
您当然可以用传统函数,但随后很难与函数指针一起使用,因为它与
CalculateAverage
具有不同的原型。有状态
由于函子可以是有状态的,因此您可以执行以下操作:
对多个不同的数据集进行平均。
请注意,几乎所有接受函子的 STL 算法/容器都要求它们是“纯”谓词,即随着时间的推移,状态没有可观察到的变化。
for_each
是这方面的一个特例(参见例如有效的标准 C++ 库 - for_each 与 Transform)。性能
函子通常可以由编译器内联(毕竟,STL 是一堆模板)。虽然理论上函数也是如此,但编译器通常不会通过函数指针进行内联。典型的例子是比较
std::sort
和qsort
;假设比较谓词本身很简单,STL 版本通常快 5-10 倍。总结
当然,可以使用传统函数和指针来模拟前三个,但使用函子会变得更加简单。
At least four good reasons:
Separation of concerns
In your particular example, the functor-based approach has the advantage of separating the iteration logic from the average-calculation logic. So you can use your functor in other situations (think about all the other algorithms in the STL), and you can use other functors with
for_each
.Parameterisation
You can parameterise a functor more easily. So for instance, you could have a
CalculateAverageOfPowers
functor that takes the average of the squares, or cubes, etc. of your data, which would be written thus:You could of course do the same thing with a traditional function, but then makes it difficult to use with function pointers, because it has a different prototype to
CalculateAverage
.Statefulness
And as functors can be stateful, you could do something like this:
to average across a number of different data-sets.
Note that almost all STL algorithms/containers that accept functors require them to be "pure" predicates, i.e. have no observable change in state over time.
for_each
is a special case in this regard (see e.g. Effective Standard C++ Library - for_each vs. transform).Performance
Functors can often be inlined by the compiler (the STL is a bunch of templates, after all). Whilst the same is theoretically true of functions, compilers typically won't inline through a function pointer. The canonical example is to compare
std::sort
vsqsort
; the STL version is often 5-10x faster, assuming the comparison predicate itself is simple.Summary
Of course, it's possible to emulate the first three with traditional functions and pointers, but it becomes a great deal simpler with functors.
Functor 的优点:
Advantages of Functors:
std::for_each
无疑是标准算法中最反复无常且最无用的。它只是一个很好的循环包装。然而,即使它也有优点。考虑您的
CalculateAverage
的第一个版本必须是什么样子。它将在迭代器上进行循环,然后对每个元素进行处理。如果你错误地编写了该循环会发生什么?哎呀;存在编译器或运行时错误。第二个版本绝对不会有这样的错误。是的,代码并不多,但是为什么我们必须如此频繁地编写循环呢?为什么不只一次呢?现在,考虑真实算法;那些真正起作用的。你想写
std::sort
吗?或者std::find
?或者std::nth_element
?您是否知道如何以最有效的方式实施它?您想要实现这些复杂算法多少次?至于阅读的难易程度,那就是仁者见仁智者见智了。正如我所说,std::for_each 几乎不是算法的首选(尤其是使用 C++0x 基于范围的 for 语法)。但如果你谈论的是真正的算法,它们就非常具有可读性;
std::sort
对列表进行排序。一些比较晦涩的内容(例如std::nth_element
)不会那么熟悉,但您始终可以在方便的 C++ 参考中查找它。一旦您在 C++0x 中使用 Lambda,甚至 std::for_each 也是完全可读的。
std::for_each
is easily the most capricious and least useful of the standard algorithms. It's just a nice wrapper for a loop. However, even it has advantages.Consider what your first version of
CalculateAverage
must look like. It will have a loop over the iterators, and then do stuff with each element. What happens if you write that loop incorrectly? Oops; there's a compiler or runtime error. The second version can never have such errors. Yes, it's not a lot of code, but why do we have to write loops so often? Why not just once?Now, consider real algorithms; the ones that actually do work. Do you want to write
std::sort
? Orstd::find
? Orstd::nth_element
? Do you even know how to implement it in the most efficient way possible? How many times do you want to implement these complex algorithms?As for ease of reading, that's in the eyes of the beholder. As I said,
std::for_each
is hardly the first choice for algorithms (especially with C++0x's range-based for syntax). But if you're talking about real algorithms, they're very readable;std::sort
sorts a list. Some of the more obscure ones likestd::nth_element
won't be as familiar, but you can always look it up in your handy C++ reference.And even std::for_each is perfectly readable once you use Lambda's in C++0x.
•与函数不同,Functor 可以有状态。
这非常有趣,因为 std::binary_function、std::less 和 std::equal_to 有一个 const 的operator() 模板。但是,如果您想打印一条包含该对象当前调用计数的调试消息,您会怎么做呢?
这是 std::equal_to 的模板:
我可以想到 3 种方法来允许 operator() 为 const,但仍更改成员变量。但最好的方法是什么?举个例子:
因为所有 3 个解决方案都被编译进来,所以计数增加了 3。输出如下:
•Unlike Functions Functor can have state.
This is very interesting because std::binary_function, std::less and std::equal_to has a template for an operator() that is const. But what if you wanted to print a debug message with the current call count for that object, how would you do it?
Here is template for std::equal_to:
I can think of 3 ways to allow the operator() to be const, and yet change a member variable. But what is the best way? Take this example:
Because all 3 solutions are compiled in, it increments count by 3. Here's the output:
在第一种方法中,必须在所有想要对集合执行某些操作的函数中复制迭代代码。第二种方法隐藏了迭代的细节。
In the first approach the iteration code has to be duplicated in all functions that wants to do something with the collection. The second approach hide the details of iteration.
OOP 是这里的关键字。
http://www.newty.de/fpt/functor.html:
4.1 什么是函子?
函子是具有状态的函数。在 C++ 中,您可以将它们实现为一个类,该类具有一个或多个私有成员来存储状态,并具有一个重载运算符 () 来执行函数。函子可以使用模板和多态性概念来封装 C 和 C++ 函数指针。您可以构建指向任意类的成员函数的指针列表,并通过同一接口调用它们,而不必担心它们的类或需要指向实例的指针。所有函数必须具有相同的返回类型和调用参数。有时函子也称为闭包。您还可以使用函子来实现回调。
OOP is keyword here.
http://www.newty.de/fpt/functor.html:
4.1 What are Functors ?
Functors are functions with a state. In C++ you can realize them as a class with one or more private members to store the state and with an overloaded operator () to execute the function. Functors can encapsulate C and C++ function pointers employing the concepts templates and polymorphism. You can build up a list of pointers to member functions of arbitrary classes and call them all through the same interface without bothering about their class or the need of a pointer to an instance. All the functions just have got to have the same return-type and calling parameters. Sometimes functors are also known as closures. You can also use functors to implement callbacks.
您正在比较不同抽象级别的函数。
您可以将
CalculateAverage(begin, end)
实现为:或者您可以使用 for 循环来实现
前者需要您了解更多的事情,但是一旦您了解了它们,就更简单并且留下的可能性更少错误。
它还只使用两个通用组件(
std::accumulate
和std::plus
),在更复杂的情况下也经常出现这种情况。您通常可以拥有一个简单的通用仿函数(或函数;普通的旧函数可以充当仿函数),然后将其与您需要的任何算法简单地组合起来。You are comparing functions on different level of abstraction.
You can implement
CalculateAverage(begin, end)
either as:or you can do it with a for loop
The former requires you to know more things, but once you know them, is simpler and leaves fewer possibilities for error.
It also only uses two generic components (
std::accumulate
andstd::plus
), which is often the case in more complex case too. You can often have a simple, universal functor (or function; plain old function can act as functor) and simply combine it with whatever algorithm you need.