问题
In some cases std::function
can replace inheritance. The following two code snippets are very similar (about the same costs when calling the function, almost the same usage in signatures and in most cases std::function does not require us to make an extra copy of A
as well):
struct Function
{
virtual int operator()( int ) const =0;
};
struct A
: public Function
{
int operator()( int x ) const override { return x; }
};
Using std::function
, we can rewrite this as
using Function = std::function<int (int)>;
struct A
{
int operator()( int x ) const { return x; }
};
In order to make more clear, how the two snippets are related: both of them can be used in the following way:
int anotherFunction( Function const& f, int x ) { return f( x ) + f( x ); }
int main( int argc, char **argv )
{
A f;
anotherFunction( f, 5 );
return 0;
}
The latter approach is more flexible, because we do not have to derive our classes from some common base class. The only relationship between Function
-like objects is based on their capabilities. In terms of object-orientated programming it might be considered less clean (but not in terms of functional programming, of course).
Apart from that, are there any other differences between the two solutions? Are there any general guidelines when to use which of the solutions or is it only a matter of personal preference? Are there any cases where one solution out-performs the other?
回答1:
A small correction first: notice, that this:
int anotherFunction( Function f, int x ) { return f( x ) + f( x ); }
Would not compile with the inheritance-based solution, since Function
is being taken by value and it is abstract. If it were not abstract, on the other hand, you would get slicing, which is not what you want.
Rather, you would have to take your Function
-derived object by reference (possibly by reference to const
) in order to take advantage of polymorphism:
int anotherFunction( Function const& f, int x ) { return f( x ) + f( x ); }
And this is not very functional-like, so if you're keen on functional programming (as you seem to be) you may want to avoid it just because of this.
That said, here is the guideline I would provide:
If you can, use templates:
template<typename F> int anotherFunction(F f, int x) { return f(x) + f(x); }
In general, when it can be used at all, static (compile-time, template-based) polymorphism is considered preferable to dynamic (run-time, inheritance-based) polymorphism, because of:
Superior flexibility: you do not have to change the definition of your types and make them derive from a common base class in order for them to be used generically. This allows you, for instance, to write:
anotherFunction([] (int x) { return x * 2; }, 42);
As well as:
anotherFunction(myFxn, 42); // myFxn is a regular function
Or even:
anotherFunction(my_functor(), 42); // my_functor is a class
Superior performance: since you are not calling through a virtual table and the compiler knows how function calls will be resolved, it can inline the call to give you greater performance (if it deems this to be reasonable).
If you cannot use templates, because the function to be invoked is going to be determined at run-time, use
std::function
:int anotherFunction(std::function<int (int)> f, int x) { return f(x) + f(x); }
This will also give you enough flexibility to pass in lambdas, function pointers, functors, basically any callable object. See, for instance, this Q&A on StackOverflow.
Using
std::function
may bring a significant run-time overhead with respect to a template-based design, and perhaps also a slight minor overhead with respect to a hardcoded inheritance-based solution like the one you outline, but it gives you flexibility and it is a standard idiom. Moreover, as always when performance is a concern, make measurements to back up any assumption - you might get surprising results.You usually need to resort to this kind of
std::function
-based design when you want to store callable objects of any type for later invocation, as in the case of the Command design pattern, or when you have a heterogenous collection of callable objects to be treated and invoked generically. For a discussion on when to usestd::function
instead of templates, see this Q&A on StackOverflow.So when should you resort to a hardcoded design using inheritance? Well, in all those cases where 1. and 2. are not viable - honestly, I can't think of any right now, but I'm sure someone could come up with a corner case. Notice, that C++11 is not necessary in order to make use of an
std::function
-like idiom, since Boost has aboost::function
and aboost::bind
implementation that pre-date (and served as an inspiration for) C++11'sstd::function
andstd::bind
.
TL;DR: Use templates or std::function
.
来源:https://stackoverflow.com/questions/16435549/when-to-use-stdfunction-instead-of-inheritance