Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functors vs. std::bind

Sometimes I tend to write functors, not for the sake of maintaining state between function calls, but because I want to capture some arguments that are shared between function calls. As an example:

class SuperComplexAlgorithm
{
    public:
        SuperComplexAlgorithm( unsigned int x, unsigned int y, unsigned int z )
            : x_( x ), y_( y ), z_( z )
        {}

        unsigned int operator()( unsigned int arg ) const /* yes, const! */
        {
            return x_ * arg * arg + y_ * arg + z_;
        }

    private:
        // Lots of parameters are stored as member variables.
        unsigned int x_, y_, z_;
};

// At the call site:
SuperComplexAlgorithm a( 3, 4, 5 );
for( unsigned int i = 0; i < 100; ++i )
    do_stuff_with( a ); // or whatever

Compared to

unsigned int superComplexAlgorithm( unsigned int x, unsigned int y,
                                    unsigned int z, unsigned int arg )
{
    return x * arg * arg + y * arg + z;
}

// At the call site:
auto a = std::bind( superComplexAlgorithm, 3, 4, 5, std::placeholders::_1 );
for( unsigned int i = 0; i < 100; ++i )
    do_stuff_with( a );

the first solution has a lot of drawbacks, in my opinion:

  • Documentation of what x, y, z do is split at different places (constructor, class definition, operator()).
  • Lots of boiler-plate code.
  • Less flexible. What if I want to capture a different sub-set of parameters?

Yes, I just realized how useful boost::bind or std::bind can be. Now for my question before I start using this in a lot of my code: Is there any situation where I should consider using a hand-written stateless functor over binding arguments in a plain function?

like image 491
Markus Mayr Avatar asked Dec 11 '22 13:12

Markus Mayr


2 Answers

The lambda solution would be the idomatic C++11 way:

auto a = [&]( int x ){ return superComplexAlgorithm( 3, 4, 5, x ); };
for( unsigned int i = 0; i < 100; ++i )
  do_stuff_with( a );

The advantage of using a hand written functor is that you can:

  1. Move arguments into the capture, or transform them (you can do that with lambda in C++1y, but not yet -- this is also possible with bind)
  2. It has a non-anonymous type (so you can talk about the type without using auto) -- the same is true of bind, but it also has a non-anonymous meaningful type you can get at without decltypeing the entire expression! C++1y again makes this better for bind/lambda.
  3. You can do perfect forwarding of the remaining arguments (you might be able to do this in C++1y with a lambda, and bind does this)
  4. You can mess around with how the captured data is stored (in a pointer? a smart pointer?) without messing up the code where you create the instance.

You can also do some extremely powerful things with hand written functors that are impossible to do with bind and lambdas, like wrap an overload set of multiple functions into one object (that may or may not share a name), but that kind of corner case isn't going to show up often.

like image 136
Yakk - Adam Nevraumont Avatar answered Dec 14 '22 02:12

Yakk - Adam Nevraumont


Is there any situation where I should consider using a hand-written stateless functor over binding arguments in a plain function?

From the comments:

I don't want to define superComplexAlgorithm at the call site, because it is super complex, is used in many places, needs testing and documentation, etc..

If the implementation of superComplexAlgorithm requires a bunch of code and you end up splitting it into different functions that take a large amount of arguments, then you might be better off with a class that provides the shared state across the internal implementation functions. Other that that corner case, the behavior of std::bind would be equivalent to the functor you provided in the question.

Some notes, since you mention boost::bind and std::bind as alternatives. You might want to test what your implementation does. For example, with boost 1.35 (ancient) the bind operation would make 4 copies of each argument, VS2010 would make 7 copies in std::bind although std::bind in gcc 4.7 did only 1. If the arguments are small that won't make up to a high cost, but if you are doing the operation or your objects are expensive to copy... just measure.

Regarding the lambda, if performance is an issue, also measure how it behaves. It should make a single copy. If performance is not an issue, then a lambda is far less explicit on what is being captured (the user needs to read the implementation of the lambda to figure that out), and even looking at the code it might not be that obvious.

like image 40
David Rodríguez - dribeas Avatar answered Dec 14 '22 02:12

David Rodríguez - dribeas