为什么在函数上使用函子?

2022-01-07 00:00:00 c++ stl functor

比较

double average = CalculateAverage(values.begin(), values.end());

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)?

假设函子定义如下:

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;
   }
};

推荐答案

至少有四个很好的理由:

At least four good reasons:

关注点分离

在您的特定示例中,基于函子的方法具有将迭代逻辑与平均计算逻辑分离的优点.因此,您可以在其他情况下使用您的函子(想想 STL 中的所有其他算法),并且您可以将其他函子与 for_each 一起使用.

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.

参数化

您可以更轻松地参数化函子.因此,例如,您可以有一个 CalculateAverageOfPowers 函子,它取数据的平方或立方体等的平均值,可以这样写:

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:

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不同的原型.

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.

状态

由于函子可以是有状态的,你可以这样做:

And as functors can be stateful, you could do something like this:

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);

对多个不同的数据集求平均值.

to average across a number of different data-sets.

请注意,几乎所有接受函子的 STL 算法/容器都要求它们是纯"谓词,即随着时间的推移没有可观察到的状态变化.for_each 是这方面的一个特例(参见例如 Effective Standard C++ Library - for_each vs. transforma>).

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).

性能

函数通常可以被编译器内联(毕竟 STL 是一堆模板).虽然理论上函数也是如此,但编译器通常不会通过函数指针内联.典型的例子是比较 std::sortqsort;假设比较谓词本身很简单,STL 版本通常要快 5-10 倍.

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 vs qsort; the STL version is often 5-10x faster, assuming the comparison predicate itself is simple.

总结

当然,可以使用传统的函数和指针来模拟前三个,但使用函子会变得更加简单.

Of course, it's possible to emulate the first three with traditional functions and pointers, but it becomes a great deal simpler with functors.

相关文章