A Simple Approach to C++ Callbacks
To create safer callbacks in C++, see Callbacks and Lambdas.
C++ callbacks can be more complex than those in C. In C, you pass a function pointer and call it like a normal function. In C++, this is not possible due to its object-oriented nature. It is easy to pass static
methods as callbacks because they are similar to C functions, but static
methods cannot access non-static class members. I have settled on an approach that simplifies C++ callbacks and makes them similar to C counterparts. The caller provides an argument to the callback, which 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 good use case for this approach. POSIX threads require a callback and an arbitrary argument, which this approach provides.
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 a value passed to start_routine
. The following class shows how a member function 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 useful for you too!