This depends on your particular use case and how the users add their code. The following is a purely theoretical analysis, but you should build a realistic scenario and test the performance before actually deciding.
If the code is to be added at compile time (i.e. you provide the code to the users, they create their logic and compile everything together) then probably the best approach is to provide a template that takes a Functor
type argument.
template <typename Functor> void doProcessing( Functor f) {
f( data );
}
The advantage is that the compiler has access to the whole code, and that means that it can take the best decisions as to whether it inlines or not the code, and might be able to optimize further in the case of inlining. The disadvantage is that you need to recompile the program with each new piece of logic and that you have to compile together your product with the user extensions, with many times it not possible.
If the extensions are to be performed after compilation of your main product, i.e. clients can produce their own extensions and use them to a compiled executable (think plugins) then you should consider the alternatives:
accept a function pointer (the C way)
provide an interface (base class) with just that operation (the Java way)
use a functor wrapper (boost::function
/std::function
) that performs type erasure
These are ordered by pure performance. The cost of the C way is the smaller of the three, but the difference with the interface option is negligible in most cases (an extra indirection per function call) and it buys you the option of keeping state in the calling object (this would have to be done through global state in the first option).
The third option is the most generic as it applies type-erasure to the user callable, and will allow users to reuse their existing code by using function adaptors like boost::bind
or std::bind
as well as lambdas if the compiler supports them. This is the most generic and leaves the most choices to the user. There is an associated cost to it though: there is a virtual dispatch in the type erasure plus an additional function call to the actual piece of code.
Note that if the user will have to write a function to adapt your interface with their code, the cost of that manually crafted adaptor will most probably be equivalent, so if they need the genericity, then boost::function
/std::function
are the way to go.
As of the difference in costs of the alternatives, they are most probably very small overall, and whether scratching one or two operations matters will depend on how tight the loop is and how expensive the user code is. If the user code is going to take a couple hundred instructions, there is probably no point in not using the most generic solution. If the loop is run a few thousand times per second, scratching a few instructions will not make a difference either. Go back, write a realistic scenario and test, and while testing be aware of what really matters to the user. Scratching a few seconds of an operation that takes minutes is not worth loosing the flexibility of the higher level solutions.