Jarx
Jarx

Reputation: 91

C++: Class member functions as event callbacks

I'm trying to add a simple messaging system to my project, where events can be invoked by a function, which will lead to all callbacks registered to that event being called.

Now, the logical way to do this is using function pointers. It would be easily possible to pass the pointer to the desired callback function to the events manager, for registering. An event callback function would always return an int and take a void* as argument.

However I don't want to register static global functions as my event callbacks - I'd like to do it with class member functions.

Upvotes: 9

Views: 10653

Answers (7)

krjw
krjw

Reputation: 4450

I am using lukes answer with SWIG because SWIG does not support all C++11 features... This probably can be improved even further with Parsa Jamshidis approach.

I modified it to cover even more cases (variable amount of arguments and variable return type):

#include <iostream>

template <typename R, typename ...T>
class Callback
{
public:
    virtual ~Callback() {}
    virtual R operator() (T... args) = 0;
};

template <typename R, typename ...T>
class FreeCallback : public Callback<R, T...>
{
public:
    FreeCallback(R(*func)(T...)) : func_(func) {}
    virtual R operator() (T... args) { return (*func_)(args...); }
private:
    R(*func_)(T...);
};

template <typename tClass, typename R, typename ...T>
class MemberCallback : public Callback<R, T...>
{
public:
    MemberCallback(tClass* instance, R (tClass::*memberfunction)(T...)) : instance_(instance), memberfunc_(memberfunction) {}
    virtual R operator() (T... args) { return (instance_->*memberfunc_)(args...); }
private:
    tClass * instance_;
    R (tClass::*memberfunc_)(T...);
};

class foo {
  public:
  Callback<int, int> *IntCallback;
  Callback<int, int, double, double> *IntDoubleDoubleCallback;
};

class blub {
  public:
  int func1(int i) {
    std::cout << "args: " << i << std::endl;
    return 1;
  }
  int func2(int i, double d1, double d2){
    std::cout << "args: " << i << " " << d1 << " " << d2 << std::endl;
    return 0;
  }
};

  int freeFunc1(int i) {
    std::cout << "args: " << i << std::endl;
    return 1;
  }
  int freeFunc2(int i, double d1, double d2){
    std::cout << "args: " << i << " " << d1 << " " << d2 << std::endl;
    return 0;
  }

int main() {
  foo f;
  blub b;
  f.IntCallback = new MemberCallback<blub, int, int>(&b, &blub::func1);
  f.IntDoubleDoubleCallback = new MemberCallback<blub, int, int, double, double>(&b, &blub::func2);

  Callback<int, int> *IntFreeCallback = new FreeCallback<int, int>(&freeFunc1);
  Callback<int, int, double, double> *IntDoubleDoubleFreeCallback = new FreeCallback<int, int, double, double>(&freeFunc2);

  int ret = (*IntFreeCallback)(42);
  std::cout << "ret freeFunc1: " << ret << std::endl;
  ret = (*IntDoubleDoubleFreeCallback)(42, 3.1415, 2.7182);
  std::cout << "ret freeFunc2: " << ret << std::endl;


  ret = (*f.IntCallback)(42);
  std::cout << "ret func1: " << ret << std::endl;
  ret = (*f.IntDoubleDoubleCallback)(42, 3.1415, 2.7182);
  std::cout << "ret func2: " << ret << std::endl;
  std::cout << "Hello World!\n";
  // cleanup not done here...
}

Upvotes: 0

luke
luke

Reputation: 37463

Yes it is possible. C++0x has the function class that handles this, and as others have pointed out Boost has similar facilities.

You can also roll your own, but the syntax is not for the faint of heart:

#include <iostream>

class Callable
{
    public:

        virtual ~Callable() {}
        virtual int operator() (void* args) = 0;
};

class CallableFreeFunction  : public Callable
{
    public:

        CallableFreeFunction(int (*func)(void*)) : func_(func) {}

        virtual int operator() (void* args) { return (*func_)(args); }

    private:

        int (*func_)(void*);
};

template <typename tClass>
class ClassMemberCallable : public Callable
{
    public:

        ClassMemberCallable(tClass* instance, int (tClass::*memberfunction)(void*)) : instance_(instance), memberfunc_(memberfunction) {}

        virtual int operator() (void* args) { return (instance_->*memberfunc_)(args); }

    private:

        tClass* instance_;
        int (tClass::*memberfunc_)(void*);
};

class Foo
{
    public:

        int derp(void* args)
        {
            std::cout << args << '\n';
            return 2;
        }
};

int freefunctionfoo(void* args)
{
    std::cout << "free" << args << '\n';
    return 2;
}

int main(int argc, char* argv[])
{
    Foo myfoo;

    Callable* callable = new ClassMemberCallable<Foo>(&myfoo, &Foo::derp);

    (*callable)(0);

    delete callable;

    callable = new CallableFreeFunction(freefunctionfoo);

    (*callable)(0);

    delete callable;

    std::cin.get();

    return 0;
}

This demonstrates a way of handling both free functions, and member functions in an opaque way. This is a simple example, and can be made more generic and robust in a number of ways. I'd refer you to these pages for syntax help:

http://www.newty.de/fpt/index.html

http://www.parashift.com/c++-faq-lite/pointers-to-members.html

I'd also recommend looking at this for more ideas:

http://www.codeproject.com/KB/cpp/FastDelegate.aspx

Upvotes: 8

fouronnes
fouronnes

Reputation: 4028

Of course it's possible ! Have a look at Boost.Signal2 and Boost.Bind.

Boost.Signal2 basically implements a signal and slots system which is exactly what you need. Then, you can use boost::bind which is a generalization of std::bind1st and std::bind2nd to get function object wrappers to basically anything you can think of (in your case, member methods). It's really powerful.

See this official boost tutorial.

Upvotes: 3

Parsa Jamshidi
Parsa Jamshidi

Reputation: 717

Here is my not-so-good attempt for doing a job like that: First of all you need a base event handler class, well let's call it EvtHandler for now:

class Event; //implement this yourself, it shall contain general but good info about event

class EvtHandler
{
public:
    virtual void handleEvent (Event & evt);
};

Then every class that is supposed to handle events in a way, should derive from this class, and they can implement new functions as much as they want as far as they return the same data type (void in this case) and recieve the same paramteres (Event in this case). Like this:

class Foo : public EvtHandler
{
public:
    void handleFooEvent (Event & event);
};

Then I implemented message centers for each special event, which had to register listeners and dispatch events when needed:

class ShutdownMessageCenter
{
typedef std::map<EventHandler *, event_func> ListenerMap; 
public:

    void register (EvtHandler * handler, void(EvtHandler::*memFunc)(Event &)) {
        m_lmap[handler] = memFunc;
    }

     void callListeners () {
         Event shutdown_event (EM_SHUTDOWN /*just imagine this can mean something, idk*/);
         ListenerMap::iterator itr = m_lmap.begin ();
         for (; itr != m_lmap.end(); ++itr) {
            EvtHandler * handler = itr->first;
            void (EvtHandler::*func)(Event &) = itr->second;
            (handler->*func)(shutdown_event);
          }
      }

private:
   ListenerMap m_lmap;
};

Then you could register your EvtHandlers to this particular message center for example!

ShutdownMessageCenter message_center;
EvtHandler * some_handler = new EvtHandler ();
Foo * some_foo = new Foo ();
message_center.register (some_handler, &EvtHandler::handleEvent);
message_center.register (some_foo, static_cast<void (EvtHandler::*)(Event &)>(&Foo::handleFooEvent);
message_center.callListeners ();

But once again this is not good at all, just thought I would share! Sorry for the mess, haha!

Upvotes: 2

mkaes
mkaes

Reputation: 14119

I am not completely sure what you want to archive but maybe you should look at Boost Signals2

It is quite helpful if you want to create some sort of Signal/Slot mechanism.

Upvotes: 1

DanS
DanS

Reputation: 1797

The closest that I have managed is to register a static member function as the callback. The static member takes the object (this) pointer as an argument in addition to the arguments sent by the event handler and uses this to call the member function.

class myClass{
public:
  static void callback(void *arg, void *obj)
  {
    if (obj)
      reinterpret_cast<myClass*>(obj)->cb(arg);
  }

private:
  void cb(void *arg);
};

Register myClass::callback and this with your handler. You may need to wrap this in the structure that arg references if you are restricted in what can be returned.

Upvotes: 0

J.N.
J.N.

Reputation: 8421

No, it is not possible (unless you do c++/cli with .net).

Now, you can still create static functions, pass them an object as a parameter, and the only thing that they'll do is call your member function on that object. (Actually a cast will be required first).

Upvotes: 0

Related Questions