Reputation: 621
I have a Class A which I intend to put in a shared library as it interacts with the device drivers.
I have a Class B and may be C,D,E... in future which will use the class A using the shared library.
I want a capability of setting a callback function in Class A so that when a specific event occurs a non-static members function of class B,C,D,E ... should be called by Class A.
I searched on google for callback function in C++ but found that non-static member functions are not supported by C-style definition of callbacks.
Can it be done using function pointers ?
Kindly give some suggestions for callbacks in C++ which do not violate the OOPS concepts.
I also came around a library called 'Boost' which offers similar functionality but I want to avoid the overhead of the extra library if possible. Is Boost recommended for callback function ?
EDIT : The B,C,D,E will not share any hierarchy and they will be completely independent classes. But all of them would have object of class A. And class A would also have a public function to set the callback function.
Upvotes: 0
Views: 2625
Reputation: 131907
One option, if you really really want to avoid the nearly unimportant overhead of a polymorphic function wrapper, is to make those functions static and have them take a "user data" void*
parameter, pointing to an appropriate instance of the class the function is a member of. Inside the static function, you then cast back to the appropriate type:
#include <iostream>
struct A{
typedef void (*callback_type)(void*, int);
callback_type callback;
void* user_data;
void set_callback(callback_type cb, void* ud){
callback = cb; user_data = ud;
}
void invoke(){ callback(user_data, 42); }
};
struct B{
static void cb_foo(void* vself, int data){
B* self = static_cast<B*>(vself);
self->foo(data);
}
void foo(int data){ std::cout << data * 2 << "\n"; }
};
struct C{
static void cb_bar(void* vself, int data){
C* self = static_cast<C*>(vself);
self->bar(data);
}
void bar(int data){ std::cout << data / 2 << "\n"; }
};
int main(){
A a;
B b;
a.set_callback(&B::cb_foo, &b);
a.invoke();
C c;
a.set_callback(&C::cb_bar, &c);
a.invoke();
}
I personally would recommend using std::function
, though, since the above is severly limited in what can be accepted as a callback. std::function
is a polymorphic function wrapper, meaning that it can take normal function pointers, member function pointers and even functors (function objects) and invoke them all in the same manner. Together with std::bind
, which allows you to bind parameters to a function, you can make easy callbacks to member functions. Boost offers them too (Boost.Function, Boost.Bind).
#include <iostream>
#include <functional> // C++11
//#include <boost/function.hpp>
//#include <boost/bind.hpp>
struct A{
std::function<void(int)> callback;
void invoke(){ callback(42); }
};
struct B{
void foo(int data){ std::cout << data * 2 << "\n"; }
};
struct C{
void bar(int data){ std::cout << data / 2 << "\n"; }
};
int main(){
using namespace std::placeholders; // namespace for argument placeholders for std::bind
// not needed for Boost.Bind
A a;
B b;
a.callback = std::bind(&B::foo, &b, _1);
a.invoke();
C c;
a.callback = std::bind(&C::bar, &c, _1);
a.invoke();
};
Basically std::bind
does automatically what you had to do manually in the first version, it saves the object pointer and invokes the member function on it. It doesn't do this through a void*
pointer, however, and instead std::bind
returns a different binder type for every different object pointer. That's why you need std::function
, since it doesn't care what you pass it.
Upvotes: 4
Reputation: 2292
Assuming that {B, C, D, E} share some hierarchy so you do not need to write a new version for each class, make the callback function static but add an additional parameter that is a reference to the {B, C, D, E} instance that is involved in the callback. That way, once you are within the context of the function, you will be able to invoke nonstatic functions/operations on the relevant object.
If the class hierarchies for {B, C, D, E} or anything that comes up in the future are not the same and you cannot find a common starting point, you will probably need to resort to something more generic like a void pointer, though that makes it very difficult to know what functionality can be invoked on the object.
Upvotes: 0