Should I use std::function or a function pointer in C++?

71,742

Solution 1

In short, use std::function unless you have a reason not to.

Function pointers have the disadvantage of not being able to capture some context. You won't be able to for example pass a lambda function as a callback which captures some context variables (but it will work if it doesn't capture any). Calling a member variable of an object (i.e. non-static) is thus also not possible, since the object (this-pointer) needs to be captured.(1)

std::function (since C++11) is primarily to store a function (passing it around doesn't require it to be stored). Hence if you want to store the callback for example in a member variable, it's probably your best choice. But also if you don't store it, it's a good "first choice" although it has the disadvantage of introducing some (very small) overhead when being called (so in a very performance-critical situation it might be a problem but in most it should not). It is very "universal": if you care a lot about consistent and readable code as well as don't want to think about every choice you make (i.e. want to keep it simple), use std::function for every function you pass around.

Think about a third option: If you're about to implement a small function which then reports something via the provided callback function, consider a template parameter, which can then be any callable object, i.e. a function pointer, a functor, a lambda, a std::function, ... Drawback here is that your (outer) function becomes a template and hence needs to be implemented in the header. On the other hand you get the advantage that the call to the callback can be inlined, as the client code of your (outer) function "sees" the call to the callback will the exact type information being available.

Example for the version with the template parameter (write & instead of && for pre-C++11):

template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
    ...
    callback(...);
    ...
}

As you can see in the following table, all of them have their advantages and disadvantages:

function ptr std::function template param
can capture context variables no1 yes yes
no call overhead (see comments) yes no yes
can be inlined (see comments) no no yes
can be stored in a class member yes yes no2
can be implemented outside of header yes yes no
supported without C++11 standard yes no3 yes
nicely readable (my opinion) no yes (yes)

(1) Workarounds exist to overcome this limitation, for example passing the additional data as further parameters to your (outer) function: myFunction(..., callback, data) will call callback(data). That's the C-style "callback with arguments", which is possible in C++ (and by the way heavily used in the WIN32 API) but should be avoided because we have better options in C++.

(2) Unless we're talking about a class template, i.e. the class in which you store the function is a template. But that would mean that on the client side the type of the function decides the type of the object which stores the callback, which is almost never an option for actual use cases.

(3) For pre-C++11, use boost::function

Solution 2

void (*callbackFunc)(int); may be a C style callback function, but it is a horribly unusable one of poor design.

A well designed C style callback looks like void (*callbackFunc)(void*, int); -- it has a void* to allow the code that does the callback to maintain state beyond the function. Not doing this forces the caller to store state globally, which is impolite.

std::function< int(int) > ends up being slightly more expensive than int(*)(void*, int) invocation in most implementations. It is however harder for some compilers to inline. There are std::function clone implementations that rival function pointer invocation overheads (see 'fastest possible delegates' etc) that may make their way into libraries.

Now, clients of a callback system often need to set up resources and dispose of them when the callback is created and removed, and to be aware of the lifetime of the callback. void(*callback)(void*, int) does not provide this.

Sometimes this is available via code structure (the callback has limited lifetime) or through other mechanisms (unregister callbacks and the like).

std::function provides a means for limited lifetime management (the last copy of the object goes away when it is forgotten).

In general, I'd use a std::function unless performance concerns manifest. If they did, I'd first look for structural changes (instead of a per-pixel callback, how about generating a scanline processor based off of the lambda you pass me? which should be enough to reduce function-call overhead to trivial levels.). Then, if it persists, I'd write a delegate based off fastest possible delegates, and see if the performance problem goes away.

I would mostly only use function pointers for legacy APIs, or for creating C interfaces for communicating between different compilers generated code. I have also used them as internal implementation details when I am implementing jump tables, type erasure, etc: when I am both producing and consuming it, and am not exposing it externally for any client code to use, and function pointers do all I need.

Note that you can write wrappers that turn a std::function<int(int)> into a int(void*,int) style callback, assuming there are proper callback lifetime management infrastructure. So as a smoke test for any C-style callback lifetime management system, I'd make sure that wrapping a std::function works reasonably well.

Solution 3

Use std::function to store arbitrary callable objects. It allows the user to provide whatever context is needed for the callback; a plain function pointer does not.

If you do need to use plain function pointers for some reason (perhaps because you want a C-compatible API), then you should add a void * user_context argument so it's at least possible (albeit inconvenient) for it to access state that's not directly passed to the function.

Solution 4

The only reason to avoid std::function is support of legacy compilers that lack support for this template, which has been introduced in C++11.

If supporting pre-C++11 language is not a requirement, using std::function gives your callers more choice in implementing the callback, making it a better option compared to "plain" function pointers. It offers the users of your API more choice, while abstracting out the specifics of their implementation for your code that performs the callback.

Solution 5

std::function may bring VMT to the code in some cases, which has some impact on performance.

Share:
71,742

Related videos on Youtube

Jan Swart
Author by

Jan Swart

Mostly doing work with a React, Redux, Styled Components front-end stack. Node.js (Express) for data aggregation layer. Scala for back-end heavy lifting. Must say, I love the stack!! Worked in a back-end C++ environment for a couple of years and I still have a very big love for C++ and C languages. At night I compose and play music, read (fiction and programming books), love the arts and walks with my dog (especially on the beach).

Updated on April 11, 2021

Comments

  • Jan Swart
    Jan Swart about 3 years

    When implementing a callback function in C++, should I still use the C-style function pointer:

    void (*callbackFunc)(int);
    

    Or should I make use of std::function:

    std::function< void(int) > callbackFunc;
    
    • Baum mit Augen
      Baum mit Augen over 9 years
      If the callback function is known at compile time, consider a template instead.
    • Pete Becker
      Pete Becker over 9 years
      When implementing a callback function you should do whatever the caller requires. If your question is really about designing a callback interface, there's nowhere near enough information here to answer it. What do you want the recipient of your callback to do? What information do you need to pass to the recipient? What information should the recipient pass back to you as a result of the call?
    • Gabriel Staples
      Gabriel Staples over 3 years
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont over 9 years
    function pointers have call overhead compared to template parameters. template parameters make inlining easy, even if you are passed down upteen levels, because the code being executed is described by the type of the parameter not the value. And template function objects being stored in template return types is a common and useful pattern (with a good copy constructor, you can create the efficient template function invokable that can be converted to the std::function type-erased one if you need to store it outside the immediately called context).
  • yo'
    yo' over 9 years
    Maybe add a line to the table showing in which standard the method was available, so that people see this information instantly?
  • leemes
    leemes over 9 years
    @tohecz I now mention if it requires C++11 or not.
  • leemes
    leemes over 9 years
    @Yakk Oh of course, forgot about that! Added it, thanks.
  • leemes
    leemes over 9 years
    @dyp Yes, I always forget about boost... Thanks for mentioning, added it.
  • Mooing Duck
    Mooing Duck over 9 years
    What overhead does std::function have that void (*callbackFunc)(int) doesn't? I believe they have the same overhead when there is no function context. std::function only has "overhead" when doing things callbackFunc can't do at all.
  • leemes
    leemes over 9 years
    @MooingDuck Of course it depends on the implementation. But if I remember correctly, due to how type erasure works there is one more indirection taking place? But now that I think about it again, I guess this is not the case if you assign function pointers or capture-less lambdas to it... (as a typical optimization)
  • Mooing Duck
    Mooing Duck over 9 years
    @leemes: Right, for function pointers or captureless lambdas, it ought to have the same overhead as a c-func-ptr. Which is is still a pipeline stall + not trivially inlined.
  • leemes
    leemes over 9 years
    @MooingDuck Read this question. I'm not sure whether or not the overhead they are talking about also applies to the case when you assign a function pointer to std::function. It is pretty obvious that there has to be an overhead when you assign a functor with context due to type-erasure.
  • Mooing Duck
    Mooing Duck over 9 years
    @leemes: the links in the accepted answer say the overhead is "similar to a standard function pointer" and "With a properly inlining compiler, an invocation of a function object requires one call through a function pointer." Second answer says "The dynamic memory allocation is what people refer the most as a performance penalty implied by std::function.".
  • leemes
    leemes over 9 years
    @MooingDuck I've read that it is comparable to a call to a virtual function (but that may refer to non-function-pointer case only, that was not very clear). The second link in the accepted answer furthermore says "If the call is to a free function pointer, an additional call must be made to that function pointer (unless the compiler has very powerful interprocedural analysis)." Hence there is an overhead of +100% (1+1 function pointer call vs 1 function pointer call) if I understand this correctly. -- To cut a long story short, I guess even if there is an overhead it is not very large.
  • leemes
    leemes over 9 years
    Also see Yakk's answer to this question, he also mentions that it is "slightly more expensive".
  • Mooing Duck
    Mooing Duck over 9 years
    @leemes: Well, pay attention to what it is that you're comparing. "If the pointer is to another pointer, an additional pointer indirection must be made" That hardly counts as overhead when it's the goal :D
  • leemes
    leemes over 9 years
    Hm, I interpreted the sentence as follows: when you wrap a function pointer to std::function, it captures it in a callable object which (when you call it) performs that additional function call. The "callable object" could be a concrete class of an abstract class being kept as a member in std::function (whose operator() is virtual) to implement type-erasure. But that sounds like an unoptimized, trivial implementation...
  • KRyan
    KRyan over 9 years
    The last two rows of the table reverse, in a sense, the meaning of 'yes' and 'no'. For all previous rows, a 'yes' indicates an advantage of that particular approach, while for the bottom two, a 'yes' indicates a disadvantage. Consider rewording them to allow 'yes' to remain the advantageous answer ("Can be implemented outside the header," "Can be used without C++11," perhaps).
  • leemes
    leemes over 9 years
    @KRyan That annoyed me too but it wasn't so important for me, now I changed it.
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont over 9 years
    Another minor point: gcc is quite good at inlining function pointers. It is better at lambdas, but quite decent even with function pointers in simple code.
  • Zitrax
    Zitrax over 7 years
    A recent article discusses the different alternatives with the addition of a function_view. Quote from that: "I strongly recommend not using std::function unless you need its general-purpose polymorphic semantics.".
  • leemes
    leemes over 7 years
    @Zitrax Thanks for sharing. The author seems to favor premature optimization, which I strongly advice to avoid. My general advice in my answer is directed to the general C++ coder which might not care about the last few percents of performance, and wether his code might heap-allocate or not. So if you don't care that much about performace, I see no reason in avoiding std::function which is very easy to be used compared to the rest. In my opinion, you should never care about constant overhead unless you found performance hurting code (by profiling). Then the question is a whole different one.
  • germannp
    germannp about 7 years
    Great answer, but what if you want to specify the signature of CallbackFunction in a template? Can you define a type CallbackFunction that can be used in a template <CallbackFuntion func> that takes functors?
  • Admin
    Admin about 7 years
    Using std::function always does not make any sense. If you can live with the old static callback, which you definitely can if plain callback is all you need, you will get faster code. Compare compiled code, the old static callback vs std::function implementation.
  • underscore_d
    underscore_d almost 7 years
    In short, use std::function unless you have a reason to not seems a very bad default. Whatever happened to not paying for what we don't use? IMO don't use std::function, or whatever feature, unless you can objectively show you need it (& then it might be great). See stackoverflow.com/questions/43948240/… @moose It's not a matter of just a static callback: lambdas & templates also exist & can suffice in many use-cases where std::function might be relatively very wasteful
  • fish2000
    fish2000 almost 7 years
    I too dislike the illegible standard syntax used for function pointers. If you are compiling under C++11 rules, you can eschew that whole style of declaration in favor of e.g. using functionptr_f = std::add_pointer_t<bool(void*, std::string const&, int)> – that will declare a function pointer, equivalent to typedef void (* functionptr_f)(void*, std::string const&, int) in the older syntax. I find the using form much more legible and expressive; it also has the advantage of being easily convertible to a std::function declaration just by replacing add_pointer_t with function.
  • Young-hwi
    Young-hwi about 6 years
    As for the last item in your advantage/disadvantage table, "nicely readable", I would like to leave a little tip for enhancing readability of C style func pointers ;-) You can typedef your function. For example, typedef int CallBackFunc(int, int); and then you can use it wherever you need a pointer to that type (say, int(int, int)) of function: CallBackFunc* func = CallBackImplementation; BTW, I don't know if it works for C++11's using type aliases as well.
  • KeyC0de
    KeyC0de over 5 years
    Where did this void* come from? Why would you want to maintain state beyond the function? A function should contain all code it needs, all functionality, you just pass it the desired arguments and modify and return something. If you need some external state then why would a functionPtr or callback carry that luggage? I think that callback is unnecessarily complex.
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont over 5 years
    @nik-lz I'm uncertain how I would teach you the use and history of callbacks in C in a comment. Or the philosophy of procedural as opposed to functional programming. So, you'll leave unfullfilled.
  • KeyC0de
    KeyC0de over 5 years
    I forgot this. Is it because one has to account the case of a member function being called, so we need the this pointer to point to the address of the object? If I'm wrong could you give me a link to where I can find more info on this, because I can't find much about it. Thanks in advance.
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont over 5 years
    @Nik-Lz member functions aren't functions. Functions have no (runtime) state. Callbacks take a void* to permit transmission of runtime state. A function pointer with a void* and a void* argument can emulate a member function call to an object. Sorry, I don't know of a resource that walks through "designing C callback mechanisms 101".
  • KeyC0de
    KeyC0de over 5 years
    Yeah, that's what I was talking about. Runtime state is basically the address of the object being called (because it changes between runs). It's still about this. That's what I meant. Ok, thanks anyway.
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont over 5 years
    @Nik-Lz Often the address of the object being called isn't important, but rather the state stored in that object is important. The address is just being used to ferry the state along.
  • KeyC0de
    KeyC0de over 5 years
    Yes exactly! I've seen a similar response of yours to another question. So for calling free functions (non-member) this void* is supposed to be left blank nullptr correct?
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont over 5 years
    @Nik-Lz No, it can carry an arbitrary payload. Member functions are nothing really special, they are just a function with an implicit first argument which is a pointer to a specific class or struct. A free function could take a pointer to a specific class or struct or even an int as an explicit argument. void increment_int(void* ptr){ int* pint = static_cast<int*>(ptr); ++*pint; } could be passed along with a void* pointing at an int to count how many times the callback is called. Or you could struct counter { int x; void increment() { ++x; } }; void counter_increment( void* ptr )
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont over 5 years
    { static_cast<counter*>(ptr)->increment(); } and get the same result and same semantics. The void* makes it type-unsafe, but when passing through a type-unaware C API some type-unsafety is unavoidable.
  • KeyC0de
    KeyC0de over 5 years
    That's right. Very useful. Thank you. Sorry for filling up the comment section(s).
  • TonySalimi
    TonySalimi about 5 years
    Can you explain hat this VMT is?
  • Barnack
    Barnack over 4 years
    thanks a lot for the table! after being confused with all te std::function and template suggestions in similar questions, your table helped me realized i actually need a good old function pointer. Question is, would there be any way to make an old school function pointer look better through a template wrapper without any overhead? Or should i just make an alias with using?
  • uss
    uss almost 4 years
    Whats the type of p here? will it be a std::function type? void f(){}; auto p = f; p();
  • Gabriel Staples
    Gabriel Staples over 3 years
    use std::function unless you have a reason to not...such as trying to be light-weight and space and time efficient on a space-constrained embedded microcontroller--because apparently std::function is pretty heavy-weight. See here ("std::function is heavy weight, so it is not a cost-less trade-off.") & here ("std::function is a complex, heavy, stateful, near-magic type...If you can get away with it, you should prefer either naked function pointers.."). So, for light-wt embedded prefer raw ptrs.
  • mic
    mic over 3 years
    Virtual method table?