Callbacks in C++11
Callbacks in C++11
Callbacks in C++11
The important thing to take away is that different users will have different requirements,
and it is difficult or impossible for the author of the algorithm to anticipate all possible
actions a user might need.
Callbacks are an ideal paradigm for dealing with this problem. At a high level, the user
defines some programatic action which can be added to the algorithm and called at a
specified time. This provides quite a bit of flexibility to the user, who can divise any
callback they wish, without needing access to the algorithm’s source code.
In c++ , callbacks are stored as “callables”: i.e., function pointers, pointers to class
methods, functors (classes which overload operator() ), and lambda functions
(since c++11 ).
Prior to c++11 , this was a relatively intimidating topic, since the syntax of function
pointers and pointers to class methods involved complicated and unintuitive syntax.
However, the advent of the auto keyword, as well as lambda functions, has greatly
simplified this topic. In this tutorial, we will cover a very simple callback example in c+
+11 . All examples were compiled with g++ 7.2.0 on Ubuntu 16.04.
A Toy Example
To begin, we define a very simple class, called SquareRoot , containing a single
method, double SquareRoot::run(const double) , which iteratively approximates
the square root of its input using the Babylonian method.
In int main() , we instantiate the class, and call run() with an example input. (We
use 1234.5*1234.5 as an input, because we know that the correct output should
be 1234.5 .)
// Includes
#include <cstdlib>
#include <iostream>
#include <math.h>
class SquareRoot {
public:
// Babylonian method.
++iteration;
return guess;
private:
int main() {
SquareRoot p;
return EXIT_SUCCESS;
$ g++ ./callbacks.cxx
$ ./a.out
Result: 1234.5
Success! But what if we want to get more information about how the algorithm is
running, such as printing out intermediate guesses? For that, we’ll use callbacks.
A TCallback instance can be used to store all the callables described above: function
pointers, pointers to class methods, functors, and lambda functions.
Since a user may want to add more than one callback, it is generally a good idea to
store a std::vector of callbacks, rather than a single one. The type of the callback
vector is defined as follows:
using TCallbackVector = std::vector<TCallback>;
We can then define a private data member to hold the callback vector…
private:
TCallbackVector m_callbacks;
m_callbacks.push_back(cb);
Finally, we add logic to the Program::run method, which invokes each callback prior
to each update:
void run() {
// ...
cb(iteration, guess);
// ...
// Includes
#include <cstdlib>
#include <iostream>
#include <math.h>
#include <vector>
#include <functional>
class SquareRoot {
public:
// Callback typedefs
m_callbacks.push_back(cb);
// Babylonian method.
cb(iteration, guess);
}
guess = (guess + input / guess) / 2.0;
++iteration;
return guess;
private:
TCallbackVector m_callbacks;
};
int main() {
SquareRoot p;
return EXIT_SUCCESS;
At this point, we have added the structure necessary to allow the user to add callbacks;
but we haven’t actually added any. So you’re welcome to compile and run this code
again, but the output should be the same.
Defining the Callbacks
For this example, we will define four callbacks: one for each type of callable discussed
above.
Function Pointer
To define a callback as a function pointer, we begin by defining a function matching the
signature we used in TCallback :
std::cout << iteration << " : " << guess << " (Function
Pointer)\n";
Moreover, since c++11 , we can use auto to easily define a function pointer, and add it
to our Program instance, p :
p.add_callback(cb_a);
By using auto , we have avoided the cumbersome function pointer syntax we would
have needed to contend with prior to c++11 .
class MemberFunctionCallback {
public:
};
auto cb = &MemberFunctionCallback::Call;
However, this cannot be passed directly to add_callback . The reason for this can be
understood by recognizing that, though the function signature of cb may appear to
match TCallback , in actuality cb takes an invisible first argument ( *this ) to a
particular instance of the class. Luckily, this can be dealt with painlessly by
using std::bind , which allows you to “bind” arguments to a callable, effectively
creating a new callable where those arguments are implied.
In this case, we bind a pointer to the class instance to the member function pointer, and
use std::placeholders::_1 to indicate that the remaining argument (the iteration
index) will be supplied during the invocation. The result is a new callable whose function
signature matches TCallback :
MemberFunctionCallback cb_b_tmp;
&cb_b_tmp, // First
argument (*this)
std::placeholders::_1, // 1st
placeholder
std::placeholders::_2); // 2nd
placeholder
p.add_callback(cb_b);
Functor
Although using std::bind is an elegant and quite readable solution to the problem of
using a member function pointer as a callback, even this small bit of added complexity
can be avoided by converting the callback in the previous section to a functor: i.e., a
class which overloads operator() .
class FunctorCallback {
public:
std::cout << iteration << " : " << guess << " (Functor)\n";
};
FunctorCallback cb_c;
p.add_callback(cb_c);
Lambda Function
The last type of callable we’ll cover is the lambda function (introduced in c++11 ).
Using auto , a lambda function instance can be easily captured and passed
to add_callback :
{ std::cout << iteration << " : " << guess << " (Lambda)\n"; };
p.add_callback(cb_d);
// Includes
#include <cstdlib>
#include <iostream>
#include <math.h>
#include <vector>
#include <functional>
std::cout << iteration << " : " << guess << " (Function
Pointer)\n";
class MemberFunctionCallback
public:
std::cout << iteration << " : " << guess << " (Member
Function)\n";
};
class FunctorCallback
public:
std::cout << iteration << " : " << guess << " (Functor)\n";
};
// Class with callback
class SquareRoot {
public:
// Callback typedefs
m_callbacks.push_back(cb);
// Babylonian method.
cb(iteration, guess);
++iteration;
return guess;
}
private:
TCallbackVector m_callbacks;
};
int main() {
SquareRoot p;
// Function Pointer
p.add_callback(cb_a);
// Member Function
MemberFunctionCallback cb_b_tmp;
&cb_b_tmp, // First
argument (*this)
std::placeholders::_1, // 1st
placeholder
std::placeholders::_2); // 2nd
placeholder
p.add_callback(cb_b);
// Functor
FunctorCallback cb_c;
p.add_callback(cb_c);
// Lambda
{ std::cout << iteration << " : " << guess << " (Lambda)\n"; };
p.add_callback(cb_d);
return EXIT_SUCCESS;
Callbacks are a striking example of how the improvements in c++11 can greatly
simplify the syntax for important programming paradigms. I hope that this post provide a
simple starting point for implementing callbacks in your own project, and perhaps that it
will spark interest in exploring c++11 (and c++14 , and c++17 , and beyond!) in greater
depth, to see how our lives as writers and readers of code can be made easier with
modern c++ . Happy coding! :-)