Search code examples
c++stlfunctor

Why use functors over functions?


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

Solution

  • 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:

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

    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:

    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.

    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 vs qsort; 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.