C++ Callback Comparisons

Today I’m going to do a quick comparison of C++ callback choices. I’ll look at C++’s native support for callbacks, as well as Boost, and PlusCallback. I wrote PlusCallback, so I’m obviously quite biased, but I’m going to demonstrate with actual code and can hopefully make a compelling case anyway.

First, we’ll define two functions to work with, one method and one plain free function. Both functions have the same signature: int return type, and one int parameter.

struct X {
    int Foo(int a);
};
 
int FreeFoo(int a);

Now, using C++’s native method pointers, we can construct a callback for the Foo method.

X x;
 
//The method pointer and object pointer are stored separately.
int (X::*callback)(int) = &X::Foo;
X* object = &x;
 
//Call x.foo(5). Note use of rare ->* operator.
(object->*callback)(5);

Of course, this callback pointer can only ever point to a method in the struct X. You can’t re-point it to a method in another class, and you can’t re-point it to a free function. This makes it worthless for any callback functionality because the caller would have to know what it’s going to call at compile time. If this information is available at compile time, you obviously don’t need callbacks. Other drawbacks include the need to store the function pointer and object pointer separately, a complex syntax, and almost no run-time flexibility.

As a side note, ->* is called the “Bind pointer to member by pointer” operator. And yes, you can overload it.


Boost provides function pointers that are oceans better.

X x;
 
//Setup callback for x.foo. Note complexity for simple task.
boost::function<int (int)> callback = std::bind1st(std::mem_fun(&X::Foo), &x);
 
callback(5); //Call x.foo(5);
 
//Change callback to free function.
callback = FreeFoo;
 
//Call FreeFunction(8).
callback(8);
 
//Boost doesn't support the < operator for function callbacks.
//assert(!(callback < callback));

boost::function isn’t too bad. It can be reset to any object or free function at run-time. The drawbacks are a huge library dependency and a complicated syntax. Also, boost::function objects can’t be sorted or stored in containers that require a sort order (like std::set and std::map).

Fun fact: the C++ preprocessor expands

#include <boost/function.hpp>

into over 30,000 lines of code.


PlusCallback supports all these features with a simple syntax.

X x;
 
//Setup callback for x.foo.
cb::Callback1<int, int> callback(&x, &X::Foo);
 
//Call x.foo(5).
callback(5);
 
//Change callback to free function.
callback = FreeFoo;
 
//Call FreeFunction(8).
callback(8);
 
//The < operator works fine.
//One can safely store these callbacks in a set or map container.
assert(!(callback < callback));

PlusCallback is contained in a single header file. It doesn’t drag any dependencies into your project, and it doesn’t need compiling. It uses the zlib license, and can be used in commercial applications freely.

8 Responses to “C++ Callback Comparisons”

  1. kevstev Says:

    how does the performance compare to boost::function? The syntax is certainly a lot nicer.

  2. Lewis Says:

    I’ll do some tests later and update the post. It’ll be interesting to know.

  3. Ben Says:

    Hi,
    looks nice, but actually your ref count code is broken. In your CallbackX::operator=:

    if (mCallback)
      mCallback->deinc();
    mCallback = rhs.mCallback;
    mCallback->inc();

    Since you deincrement the refcount before incrementing, the code does not work in case of self-assignment:

    cb::Callback1 callback(&func);
    callback(4);
    callback = callback;
    callback(4); // SEGFAULT!

    Once mCallback->deinc() is called, the ref counted object is destroyed leaving callback pointing to a dead object.

    The correct sequence would be:

    rhs.mCallback->inc();
    if (mCallback) mCallback->deinc();
    mCallback = rhs.mCallback;
  4. Lewis Says:

    Ben, thank you very much for the bug report. This was just a rookie mistake on my part.

    I added the fix and a test to detect the problem.

  5. Lewis Says:

    PlusCallback has been updated with an optimization, and there is a benchmark here.

  6. Michael Says:

    I’m going to venture out on a limb here and see if I can get a bit of help. I am a rookie at this C++ language, and I have written a few straightforward programs to accomplish a few different tasks. Most of them have used the libusb library to interface with various usb devices. But all of them so far use the synchronous communication interface which does not use callbacks.

    I now have a device which requires that I use the asynchronous interface. Therefore I am trying (rather slowly) to understand templates, function pointers, etc. in c++.

    To the point, tho:

    The libusb library (written in c) requires me to create a structure and then set a member of this structure to a pointer to a function with the following definition:

    typedef void(* libusb_transfer_cb_fn)(struct libusb_transfer *transfer)

    I have tried several different syntaxes, methods, etc. and I seem unable, with my limited knowledge, to make this work. I have tried member functions as well as static functions, and I cannot seem to get the required function to execute when the appropriate time comes.

    Would PlusCallback assist me in this, and if so, would you be willing to provide an example as to how?

  7. Lewis Says:

    Michael:

    No, PlusCallback back won’t help you. libusb is a C library, so it’s using C callbacks.

    The pointer typedef you have, is a pointer to a function returning void and taking one argument. The single argument is a pointer to a libusb_transfer struct. So just define a function following those guidelines. It must be a static or free function. Member functions aren’t supported by C.

    To pass the function’s address around, generally you just use its name.

    BTW, StackOverflow is an excellent site for these types of questions. You would probably get this question answered there in about 90 seconds flat.

  8. Michael Says:

    Thank you Lewis. I’m checking out StackOverflow now.

Leave a Reply