Functions¶
There are many ways to store a reference to a function. This document gives an overview of them and gives recommendations for when to use each approach.
- Pass function pointer and user data (as void *) separately:
- The only method that is compatible with C interfaces.
- Is cumbersome to work with in many cases, because one has to keep track of two parameters.
- Not type safe at all, because of the void pointer.
- It requires workarounds when one wants to pass a lambda into a function.
- Using
std::function
:- It works well with most callables and is easy to use.
- Owns the callable, so it can be returned from a function more safely than other methods.
- Requires that the callable is copyable.
- Requires an allocation when the callable is too large (typically > 16 bytes).
- Using a template for the callable type:
- Most efficient solution at runtime, because compiler knows the exact callable at the place where it is called.
- Works well with all callables.
- Requires the function to be in a header file.
- It's difficult to constrain the signature of the function.
- Using
blender::FunctionRef
(source):- Second most efficient solution at runtime.
- It's easy to constrain the signature of the callable.
- Does not require the function to be in a header file.
- Works well with all callables.
- It's a non-owning reference, so it cannot be stored safely in general.
The following diagram helps to decide which approach to use when building an API where the user has to pass a function.
flowchart TD
is_called_from_c["Is called from C code?"]
is_stored_when_function_ends["Is stored when function ends?"]
is_call_performance_bottleneck["Call is performance bottleneck?"]
use_function_pointer["Use function pointer and user data (approach 1)."]
use_std_function["Use `std::function` (approach 2)."]
use_template["Use `template<typename Fn>` (approach 3)."]
use_function_ref["Use `blender::FunctionRef` (approach 4)."]
is_called_from_c --"yes"--> use_function_pointer
is_called_from_c --"no"--> is_stored_when_function_ends
is_stored_when_function_ends --"yes"--> use_std_function
is_stored_when_function_ends --"no"--> is_call_performance_bottleneck
is_call_performance_bottleneck --"no"--> use_function_ref
is_call_performance_bottleneck --"yes"--> use_template
blender::FunctionRef
is preferred over std::function
when both are applicable because it's
cheaper.
- It never requires an allocation, no matter how many variables are captured.
- It's less expensive to call.
- It's less expensive to move/copy around.