C++ Callbacks (and giving member functions to C-style callbacks)
Callbacks are functions which are passed to other functions (or modules, libraries e.t.c) that then call the function at their choosing.
C++, being a strongly-typed object-orientated language, makes callbacks a tricker subject to deal with than say, in C (non-object orientated) or Javascript (object orientated but NOT strongly typed). This is especially true for embedded systems where you cannot always rely on having newer C++ standard library header files such as <functional>
at your disposal (it’s a mixed bag, some embedded C++ environments I’ve used do support <functional>
, others do not).
Terminology
First, let’s get some terminology out of the way:
Term | Description |
---|---|
Callee | A function/method/object which gets called by the caller. |
Caller | An object which gets passes a callback function, and then calls (executes) it. |
Function | A basic function that does not require an instance of a class to run (e.g. standard C style functions, or static member functions). |
Method | A function that belongs to an class, and requires an instance of that class to run. |
Signals | Term used for “events” in an event/listener system. |
Slots | Term used for objects which listen to signals in an event/listener system. These are normally implemented with a callback system. |
The Primitive C++ Callback: The Caller Knows The Type Of The Callee
The problem arises when you want to pass in a non-static method (function belonging to an class, that requires an instance of that class) as a callback to a library. A method is a member function of an object. To call a method, you can’t just know the functions memory address and call it, you also have to know the object that the function belongs to (the this
pointer!).
This means that for C++ method callbacks in their most primitive form, the callee has to know the type of the object the function belongs to. For example:
(run this code at https://replit.com/@gbmhunter/pure-cpp-method-callback#main.cpp):
Using Static Methods Or Non-Member Functions (C-Style)
If you are stuck with a C-style callback, there is no direct way to call non-static (i.e. takes a this
pointer as the magic first parameter) member function. You can however easily call static member functions (they are no different in type signature to C-style functions):
(run this code at https://replit.com/@gbmhunter/c-callback-in-cpp-using-static-method#main.cpp):
Note the main limitation of the above method is that no non-static member functions can be called. This limits how “object orientated” you can get with your software/firmware architecture.
Using Static Methods Or Non-Member Functions, But Calling Member Functions Through Shared Variables
As we touched on before, if you are stuck with a C-style callback, there is no direct way to call a member function. However, given we can call static methods (as shown directly above), we can use shared variables (e.g. file scoped variables) to call a particular instance from that static function. This is about the best you can do when you can’t change the type signature of the C-style callback.
Let’s go through an example, this time using a standard C function rather than a static member function purely for illustration they are interchangeable:
(run this code at https://replit.com/@gbmhunter/c-callback-in-c-using-global-vars-and-funcs):
Static Variables, With Templating
A slightly more complicated but flexible approach to the above is to use templating, std::bind
and std::function
as shown in the below example:
(run this code at https://replit.com/@gbmhunter/c-callback-in-cpp-using-templating-functional-bind#main.cpp):
Using std::function
If you have authorship of the library wanting to callback, then you can do even better than described above. What I would recommend here is to change the signature away from a C-style callback and use std::function
and lambdas instead. Rather than the library accepting a C-style callback in the format int (callback*) (int num1, int num2)
, update the library to accept a std::function<int(int, int)> callback
. This allows you to pass in a lambda, which as you’ll see below, allows you to easily call a member function.
(run this code at https://replit.com/@gbmhunter/c-callback-in-cpp-using-std-function-lambda#main.cpp):
Rather than a lambda like in the example above, you can use std::bind
instead. I strongly recommend the lambda way of doing things, but for sake of completeness let’s introduce the std::bind
technique
(run this code at https://replit.com/@gbmhunter/c-callback-in-cpp-using-std-function-bind#main.cpp):
C++ Callback Libraries
cpgf
Website: http://www.cpgf.org/
- Uses the signals and slots syntax
- Callbacks can be functions, member methods, virtual methods…
- Really easy to use syntax.
- Powerful.
License: Apache License, Version 2.0
libsigc++
Website: http://libsigc.sourceforge.net/
- Supports signals and slots.
- Many features.
- Powerful.
- Uses advanced C++ compiler features.
- Somewhat complex to use
License: GNU Library General Public License
Vlpp
Vlpp is an open source C++ library which provides cross-platform replacements for <functional>
(among other std libraries). Sometimes, the <functional>
library provided by the C++ standard library will not work on embedded systems (I’ve had it work fine on some embedded platforms and not on others). Vlpp can be used as a substitute, allowing you to use vl::Func<void(void)>
to replace std::function<void(void)>
and implement callbacks in this manner on embedded platforms.
External Resources
Callbacks In C++ Using Template Functors is a great page explaining and analysing all the different ways for implementing callbacks in C++. This includes the functional model, single rooted hierarchy, parameterize the caller, callee mix-in, and functors (which they promote).
The Type-safe Callbacks In C++ library on the Code Project gives a great, complete callback library for C++ which allows callbacks with 0 to 5 input arguments.
Functors to Encapsulate C and C++ Function Pointers is a short and simple tutorial on using functors.