Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 13

Callbacks in C++11

Nov 30, 2017

Motivation for Callbacks


Imagine that you have a long-running algorithm which takes many iterations to
complete. Typically, you will want to provide some kind of feedback to the user to
indicate that progress is being made. Otherwise, there is no way of distinguishing
between an application that is happliy crunching numbers, and one that is hanging on a
dropped network connection. Importantly, not all users will require the same type of
feedback. An update could be just about anything you could think of, including:

 Incrementing an iteration counter at the terminal.


 Updating a progress bar.
 Writing intermediate results to disk.
 Updating a user interface.

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 with callback

class SquareRoot {

public:

// Main logic of class.

double run(const double input) {

if (input < 0.0) throw 0; // Error checking.

this->iteration = 0; // Reset iteration number.

double guess = input; // Set initial guess to input.

// Babylonian method.

while (std::fabs(guess - input/guess) > this->epsilon) {

guess = (guess + input / guess) / 2.0;

++iteration;

return guess;

private:

const double epsilon = 1e-6; // Maximum Allowed Error.

size_t iteration = 0; // Iteration Number.


};

int main() {

SquareRoot p;

std::cout << "Result: " << p.run(1234.5*1234.5) << std::endl;

return EXIT_SUCCESS;

Let’s save the code as  callbacks.cxx , compile, and run:

$ 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.

Defining a Callback Mechanism


Let’s now define our callback mechanism. For this simple example, our callbacks will
take the iteration index and to the intermediate guess (const size_t, const double), and
return  void . To do this, we will use the  std::function  template (defined in
the  <functional>  header):

using TCallback = std::function<void(const size_t, const double)>;

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:

// Data member holding callbacks.

TCallbackVector m_callbacks;

…and a class method to add callbacks to the vector.

// Push callbacks onto the stack.

void add_callback(TCallback cb) {

m_callbacks.push_back(cb);

Finally, we add logic to the  Program::run  method, which invokes each callback prior
to each update:

// Main logic of class, where callbacks are invoked.

void run() {

// ...

for (const auto &cb : m_callbacks) {

cb(iteration, guess);

// ...

We can now put it all together into a complete  SquareRoot  class:

// Includes

#include <cstdlib>

#include <iostream>
#include <math.h>

#include <vector>

#include <functional>

// Class with callback

class SquareRoot {

public:

// Callback typedefs

using TCallback = std::function<void(const size_t, const


double)>;

using TCallbackVector = std::vector<TCallback>;

// Push callbacks onto the stack.

void add_callback(TCallback cb) {

m_callbacks.push_back(cb);

// Main logic of class.

double run(const double input) {

if (input < 0.0) throw 0; // Error checking.

this->iteration = 0; // Reset iteration number.

double guess = input; // Set initial guess to input.

// Babylonian method.

while (std::fabs(guess - input/guess) > this->epsilon) {

for (const auto &cb : m_callbacks) {

cb(iteration, guess);

}
guess = (guess + input / guess) / 2.0;

++iteration;

return guess;

private:

const double epsilon = 1e-6; // Maximum Allowed Error.

size_t iteration = 0; // Iteration Number.

// Data member holding callbacks.

TCallbackVector m_callbacks;

};

int main() {

SquareRoot p;

std::cout << "Result: " << p.run(1234.5*1234.5) << std::endl;

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 :

void FunctionPointerCallback(const size_t iteration, const double


guess) {

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 :

auto *cb_a = FunctionPointerCallback;

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 .

Pointer to Member Function


While a function is simple and convenient, it is sometimes useful to have the flexibility of
a full class instance (if, for example, you need to store data or organize functionality into
multiple methods). Defining such a callback is as simple as defining a class (again,
keeping in mind that the function signature of the callback method must match that
of  TCallback ):

class MemberFunctionCallback {

public:

void Call(const size_t iteration, const double guess) {


std::cout << iteration << " : " << guess << " (Member
Function)\n";

};

Defining a pointer to a member function is simple using  auto :

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;

auto cb_b = std::bind(&MemberFunctionCallback::Call, // function

&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:

void operator()(const size_t iteration, const double guess) {

std::cout << iteration << " : " << guess << " (Functor)\n";

};

Since an instance of a functor is itself a callable (unlike a method pointer), it can be


passed directly to  add_callback :

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 :

auto cb_d = [](const size_t iteration, const double guess)

{ std::cout << iteration << " : " << guess << " (Lambda)\n"; };

p.add_callback(cb_d);

Putting it All Together


In this post, we’ve discussed a motivating problem which callbacks address;
constructed a toy example for experimenting with their implementation; defined a simple
callback mechanism; and shown four types of callables compatible with this
mechanism. Here is the completed example, demonstrating how all the pieces we’ve
discussed fit together.

// Includes

#include <cstdlib>

#include <iostream>
#include <math.h>

#include <vector>

#include <functional>

void FunctionPointerCallback(const size_t iteration, const double


guess) {

std::cout << iteration << " : " << guess << " (Function
Pointer)\n";

class MemberFunctionCallback

public:

void Call(const size_t iteration, const double guess) {

std::cout << iteration << " : " << guess << " (Member
Function)\n";

};

class FunctorCallback

public:

void operator()(const size_t iteration, const double guess) {

std::cout << iteration << " : " << guess << " (Functor)\n";

};
// Class with callback

class SquareRoot {

public:

// Callback typedefs

using TCallback = std::function<void(const size_t, const


double)>;

using TCallbackVector = std::vector<TCallback>;

// Push callbacks onto the stack.

void add_callback(TCallback cb) {

m_callbacks.push_back(cb);

// Main logic of class.

double run(const double input) {

if (input < 0.0) throw 0; // Error checking.

this->iteration = 0; // Reset iteration number.

double guess = input; // Set initial guess to input.

// Babylonian method.

while (std::fabs(guess - input/guess) > this->epsilon) {

for (const auto &cb : m_callbacks) {

cb(iteration, guess);

guess = (guess + input / guess) / 2.0;

++iteration;

return guess;
}

private:

const double epsilon = 1e-6; // Maximum Allowed Error.

size_t iteration = 0; // Iteration Number.

// Data member holding callbacks.

TCallbackVector m_callbacks;

};

int main() {

SquareRoot p;

// Function Pointer

auto *cb_a = FunctionPointerCallback;

p.add_callback(cb_a);

// Member Function

MemberFunctionCallback cb_b_tmp;

auto cb_b = std::bind(&MemberFunctionCallback::Call, // function

&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

auto cb_d = [](const size_t iteration, const double guess)

{ std::cout << iteration << " : " << guess << " (Lambda)\n"; };

p.add_callback(cb_d);

std::cout << "Result: " << p.run(1234.5*1234.5) << std::endl;

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! :-)

You might also like