A Simple Approach to C++ Callbacks


Introducing the Stratify Toolbox

Are you a printf()-er or a code-stepper? The Stratify Toolbox is a printf()-ing awesome debug tool!

Check it Out!

Callbacks in C++ can be quite tricky compared to C. In C, you simply pass a function pointer, then call it like a normal function whenever you like. The object-oriented nature of C++ doesn’t allow such a simple approach. It is easy to pass static methods as callbacks because they are very similar to C functions. But static methods don’t allow access to non-static members of the class. This can be a bit of a pain. I have settled on an approach to C++ callbacks that makes them almost as simple as their C counterparts.

With every callback, one of the arguments is provided by the caller and passed to the callback. For C++ classes, the argument is a pointer to the object that should execute the callback.

The following is an example using some simple math callbacks.

//this takes a callback argument, 
//context needs to be provided by the caller and passed to the callback
int do_math(
  void * context, 
  int a, 
  int b, 
  int (*operation)(void * context, int a, int b)
  ){
  return operation(context,a,b);
}

class MyClass {
public:
  static int add_callback(void * context, int a, int b){
    //no access to member variables here
    return reinterpret_cast<MyClass*>(context)->add(a,b);
  }

  static int subtract_callback(void * context, int a, int b){
    //no access to member variables here
    return reinterpret_cast<MyClass*>(context)->subtract(a,b);
  }

  int add(int a, int b){
    //access to member variables is OK here
    return a + b + m_offset;
  }

  int subtract(int a, int b){
    //access to member variables is OK here
    return a - b + m_offset;
  }

   int m_offset;
}

int main(int argc, char * argv[]){
  MyClass no_offset;
  no_offset.m_offset = 0;

  MyClass offset;
  offset.m_offset = 10;

    //the compiler won't allow passing MyClass::add() directly because it is non-static
  do_math(&no_offset, 
    5, 
    5, 
    MyClass::add_callback); //returns 5+5 + 0
  do_math(&offset, 
    5, 
    5, 
    MyClass::subtract_callback); //return 5-5 + 10

  return 0;
}

Creating threads is a great use-case for this approach as well. POSIX threads need a callback and an arbitrary argument (just what we need for this approach to work).

int pthread_create(pthread_t * thread,
  const pthread_attr_t * attr,
  void *(*start_routine)(void *),
  void *arg);

The start_routine is a callback, and arg is an arbitrary value that gets passed to start_routine. The following class shows how a method can be executed in the current thread or a new one.

#include <pthread.h>

class MyClass {
public:
  static void * do_some_work_in_a_thread(void * context){
    return reinterpret_cast<MyClass*>(context)->do_some_work();
  }

  void * do_some_work(){
    //this can be run in a thread or the current context.
    return nullptr;
  }
}

int main(int argc, char * argv[]){
  MyClass working_class;
  pthread_t thread;
  working_class.do_some_work(); //execute in current thread
  pthread_create(&thread, 
    nullptr, //use default thread attributes
    MyClass::do_some_work_in_a_thread, 
    &working_class);
  //working_class.do_some_work() will be executing in a new thread

  //... wait for thread to complete
  return 0;
}

I hope this technique is as useful for you as it has been for me!

X

Thanks for Coming!

Subscribe to news and updates