Callbacks are tremendously useful in object-oriented design when one needs to decouple two classes but let them be connected via a single function call. Examples abound and at some point I'll expand. For now, this is a place for me to keep my experiments with various approaches to implementing callbacks in C++.
Wouldn't it be nice if C++ supported callbacks directly? Let's say we have a timer class that takes a callback which it will call periodically. It would be great to be able to do this:
Every second, the Timer object t
would call the connected callback function myObject.foo()
.
Unfortunately, C++ does not offer anything like this. myObject.foo
is a combination of the object pointer &myObject
and the member function pointer foo
. C++ does not have a pointer type that combines these two pointers.
One technique for implementing callbacks is to use an interface class. An interface class contains a member function to be overridden by a deriver. This is the callback function.
The class that wants to be called back derives from the CallbackInterface and implements the callback function.
Now a pointer to callee can be passed to a function or object that will call it back.
And now within Caller, we can store the callback (as a CallbackInterface*) and call it anytime we need to.
This approach takes advantage of inheritance and polymorphism to implement a callback. This works especially well if the callback interface requires more than one function. The problem with this approach is when we want to connect a single callee to a number of instances of the same caller. As an example, if we want to create three or four timers and handle each one in a different callback function (e.g. onOneSecondTimer(), onTwoSecondTimer(), ...). Since the interface class forces us to use a specific function name, we cannot do this.
callback1.cpp - Complete example.
In C, function pointers are the easiest way to implement callbacks and they can be made to work in a C++ class, although it is a little awkward. We'll need two functions to pull this off. The first is a static callback function, and the second is a member callback function.
The trick here is that staticCallbackFunction() assumes that "p" is a pointer to the Callee object. It uses that to get back into the object and call the member function callbackFunction. In main():
So, we send both the pointer to the function (Callee::staticCallbackFunction) and the "this" pointer for the object (&callee) to the class that will do the calling back. This is everything we need to get back in.
Caller just needs to store the "this" pointer and the function pointer. It can then use them when doing the callback.
For use in C++ code, this is a fairly cumbersome approach. You've got to define two functions for every callback: the static function and the actual callback function. When interfacing with C code that uses function pointers for callbacks, this is a perfect approach. It successfully makes the jump from C to C++.
callback3.cpp - Full example.
TODO: Investigate making the void* something more typesafe. Or using dynamic_cast<>. Actually, the void* is for compatibility with C APIs, so perhaps that should be mentioned.
C++11 introduces many handy new features to the language. One of them is lambda functions. A lambda function is an anonymous, temporary, usually small function. By combining the new std::function with a lambda function that looks very similar to the above function pointer approach, we can have a pretty decent callback mechanism.
The Callee in this case is a normal member function. Note that I've thrown in a member m_i to show that the "this" pointer is indeed correct.
In main(), we see the connection being made with a lambda. Like the static function in the C function pointer approach, the lambda captures the "this" pointer so that it can get into the class. The lambda syntax is a tad obtuse. This is probably the only real drawback to this approach.
If you aren't familiar with lambda functions in C++, here's a quick breakdown. "[&callee]" says to "capture" callee by reference. This basically means to make the variable "callee" available within the lambda function. We do this by reference to avoid a copy which would be disastrous. "(int i)" says that our lambda function takes a single int parameter. This matches callbackFunction(). Within the braces is the code for the lambda function. In this case we simply delegate the call to callbackFunction(). Note that we do not specify the return type of our lambda function because C++11 is clever and will figure it out automatically.
The caller uses the straightforward std::function to store the callback and the actual call is very simple.
This is so close to perfection. The only drawback is that the lambda function syntax is slightly bizarre. Other than that, the rest of this is pretty much perfect.
Note: std::bind() can be used in a similar manner, but it's even harder on the eyes and can be dangerous. The arguments to std::bind() are usually the address of the member function, the address of the object, and a list of placeholders (_1, _2, etc...) for the function parameters:
I would say that Rich Hickey's template functor callback approach is slightly better than C++11's lambda functions. It's more direct and closer to the ideal solution. It also works with pre-C++11 compilers. I've used this for many years and recommend it.
There are two key differences between the lambda functions and Rich Hickey's template functor approach. First, instead of using std::function, we need to use the slightly more clunky CBFunctor* types:
Lastly, we use makeFunctor() to create the callback. This is somewhat easier to understand than the lambda syntax.
Note that the above makeFunctor() does not have the extra first parameter to differentiate between the functor types. I'll let Rich explain from his article: "I must come clean at this point, and point out that the syntax above for makeFunctor() is possible only in the proposed language, because it requires template members (specifically, the Functor constructors would have to be templates). In the current language the same result can be achieved by passing to makeFunctor() a dummy parameter of type ptr-to-the-Functor-type-you-want-to-create. This iteration of the callback library requires you pass makeFunctor() the dummy as the first parameter. Simply cast 0 to provide this argument." My current example code has this dummy parameter as I've not yet found a version of this callback header that supports the above syntax. A search should turn up a version of the header that can handle this. I'm pretty sure I've seen them go by. (Template members are covered on pg 672 of Lippman 2013 if you want to give it a shot. Might be worth some fiddling.)
TODO: I seriously need to hunt down a more modern implementation of Template Functors that has the simpler makeFunctor() argument list. Then I need to clean it up and modernize it. Perhaps put it up on sourceforge or github. Oh, and try to re-create the .cpp file too. A test suite would be nice.
Callbacks in C++ Using Template Functors (Rich Hickey 1994) - Covers the interface class approach (Callee Mix-In) and the function pointer approach (Function Model).
There are other C++ callback libraries out there. I need to look around a bit and pull some of them into here for analysis. A Google search should turn them up pretty quickly.
Copyright (C) 2013-2020, Ted Felix
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. See http://www.gnu.org/licenses/fdl.html for the full text of this license.
<- Back to my software page.