Why can C++ functors be preferable to objects with named methods?
I recently have got excited by functors and been using them all over the place. Then the situation arose where I needed my functor to perform two different operations and I thought about adding another method to my functor (not overloading the () operator). Whether this is bad practice or not I am not sure (perhaps you could tell me), but it got me thinking about why I am using functors in the first place and not just objects. So my question is:
Is there anything special about overloading the () operator or is it just very slightly more syntactically appealing than using normal named methods?
Update:
Firstly, I know why functors may be preferable to function pointers as explained in other questions. I want to know why they can be preferable to objects with named methods.
Secondly, as for an example of when I wanted to use another possibly named method of my functor: Basically I have two functions, one which calculates something called the modularity of a graph partition - compute_modularity开发者_如何学JAVA()
, and another which computes the gain in modularity after some change of the partitioncompute_modularity_gain()
. I thought I could pass these functions as part of the same functor into an optimisation algorithm, with the gain as a named function. The reason I don't just pass two functors into the algorithm, is that I want to enforce that compute_modularity_gain()
is used only in conjuction with compute_modularity()
and not another functor e.g. compute_stability()
(which should only be used with compute_stability_gain()
. In other words, the gain function must be tightly coupled with its sibling function. If there is another way I can enforce this constraint then please let me know.
The reason for the overload of operator()
is to make functors have the same call semantics as function pointers -- and in fact you can use function pointers if you so desire.
There are a few reasons for overloading operator()
instead of using a function -- but the most important one is that compilers will rarely optimize away the indirect function call when using a function pointer, but they'll almost always optimize away the operator()
call -- this is why std::sort
usually beats std::qsort
.
There are a whole bunch of complicated reasons for this but what it really boils down to is that most (no?) compilers implement the optimization possible of removing the function pointer call, which is expensive on modern hardware.
Then the situation arose where I needed my functor to perform two different operations
Then it's no longer a functor. Either pass two functors to do what you want, or define a template method class. (You could also use mixins to achieve the template method pattern in C++ without runtime overhead -- but the Wikipedia article doesn't cover this) (Note also: Not the same as C++ templates, though C++ templates can be involved if you go the AOP route)
The basic intent behind a functor is to decouple the code that knows how to perform some kind of work from the code that knows when that work needs to be done (the classic example is associating a functor with a UI button).
One minor benefit of the functor model is that plain old function pointers are already functors. No extra work is required to wrap them. I consider this a minor benefit because a) a function pointer is slightly less efficient than wrapping a direct call to the function, and b) I find that I almost always need to bind some form of state to whatever I'm wrapping, even if it's just the this
pointer of a member function.
The key advantage of a unary interface is that it serves as a lingua franca for producers and consumers of functors. You could, say, define functors to all have an invoke()
member function, but then some other crowd would decide to standardise on do()
, and yet another might go for call()
. And all of these solutions involve more typing.
Also, multiple member functions on a single "functor" are never strictly required. If some code needs to invoke multiple distinct operations, you can simply pass multiple functors. This provides good flexibility, since the operations may be coupled, or they may be completely unrelated.
A decoupled example is a hash table that needs an equality-comparator and a hash function. In this case, the two functions could be unrelated: wrap the class's operator==()
for equality and wrap a free function to compute the hash.
A coupled example is a UI component that emits several distinct events. A single class might respond to all the events, or different classes might respond to different groups of events. Functors make it easy to choose either model, whereas requiring a single "interface" that defines callbacks for all the component's events is more awkward. Functors also make it much easier if a single object wants to handle events from two components differently, since you can give each component a different set of functor-wrapped member functions.
Finally, wrapping existing functionality in a functor is well understood and widely supported by libraries such as boost.bind, whereas creating throw-away classes that implement doX()
and doY()
isn't. Plus, the new standard adds lambdas, which dramatically simplify the creation of functors.
The only thing special about functors is that they can be used in the same way as functions. However functors may also have information injected through their constructor.
You might also want to look into std::function (or boost::function if your compiler does not support it yet) that can be used to adapt any type of object with a matching call signature.
std::bind or boost::bind lets you associate concrete arguments with a function's parameters, this allows for the same effect as passing them in through the constructor of a functor. You can even use bind to provide the this pointer to member functions so that they can be called in the same way as a plain functor, without specifying an object explicitly.
精彩评论