dotminic
dotminic

Reputation: 1145

C++ callback system

I'm trying to make a simple event dispatcher system. To put it simply, I have event classes that all derive a IEvent interface. I also have an EventManager class with a couple of methods:

void    addListener( int eventID, std::function<void( IEvent *event )> callback );
void    dispatchEvent( int eventID, IEvent *event );

The EventManager has a map of vectors:

std::map<int, std::vector<std::function<void( IEvent * )> > >   m_events;

Any object can then register via the addListener method, giving it a callback, and it will be notified whenever someone calls dispatchEvent with the same event type:

// add an event listener
EventManager::addListener(Event_MouseMove, std::bind(&Obj::callback, this, std::placeholders::_1));

// the callback method
void Obj::callback( IEvent *e )
{
    // here I need to cast e to a MouseEvent *
    // I want to get rid of this.
    MouseEvent *me = (MouseEvent *)e;
}

When calling dispatchEvent, the caller must pass an IEvent derived class, MouseEvent for example:

// dispatching an Event
EventManager::dispatchEvent(Event_MouseMove, new MouseEvent());

My problem has to do with the callback method. Say you want to listen for a MouseEvent, the object would have a method like such:

void    eventHandler( MouseEvent *event );

The problem is that the callback can not be passed to the addListener method as it is expecting an IEvent * and not a MouseEvent *. To make it work, I just use IEvent types and then the event handler method must cast that IEvent * to the derived type it is expecting; in this case a MouseEvent *. How can I make it that the addListener method take a pointer to a method that will receive any IEvent derived class ?

As an example:

// add an event listener
EventManager::addListener(Event_MouseMove, std::bind(&Obj::callback, this, std::placeholders::_1));

// the callback method
void Obj::callback( MouseEvent *e ) { ... }

// dispatching an Event
EventManager::dispatchEvent(Event_MouseMove, new MouseEvent());

Upvotes: 1

Views: 2958

Answers (4)

Frei Zhang
Frei Zhang

Reputation: 417

It's easy to solve this problem. Just add a callback interface, eg Callable. See my code example below, this class is act as Callable<void, Event*>, and wrap any subclass of Event.

template<typename Arg, /*traits here*/>
class FunctionWrapper : public Callable<void, Event*>
{
 public:
  FunctionWrapper(Callable<void, Arg*> *func)
  {
    mInternal = func;
  }

  FunctionWrapper(void (*func)(Arg*))
  {
    mInternal = new Function<void(Arg*)>(func);
  }

  template<typename T>
  FunctionWrapper(T* obj, void (T::*func)(Arg*))
  {
    mInternal = new Function<void(T::*)(Arg*)>(obj, func);
  }

  void call(Event *e)
  {
    return mInternal->call(static_cast<Arg*>(e));
  }

  void operator ()(Event *e)
  {
    return call(e);
  }

  operator void*() const
  {
    return mInternal ? 0 : this;
  }

  operator bool() const
  {
    return mInternal != 0;
  }

  bool operator==(const FunctionWrapper& wrapper) const
  {
    return mInternal == wrapper.mInternal;
  }

  bool operator!=(const FunctionWrapper& wrapper) const
  {
    return mInternal != wrapper.mInternal;
  }

  virtual ~FunctionWrapper()
  {
    if(mInternal){
      delete mInternal;
      mInternal = 0;
    }
  }
 private:
  Callable<void, Arg*> *mInternal;

  FunctionWrapper(const FunctionWrapper& wrapper);
  const FunctionWrapper& operator=(const FunctionWrapper& wrapper);
};

Upvotes: 0

Evan Teran
Evan Teran

Reputation: 90422

This may not be what you are looking for, but why not do a different approach. Instead of storing functions, just store pointers (or smart_ptrs) to objects which have a handleEvent function. Something like this:

struct EventHander {
    virtual ~EventHandler() {}
    virtual void handle_event(IEvent *e) = 0;
};

struct Myhandler : public Eventhandler {
    virtual void handle_event(IEvent *e) {
        // Do whatever
    }
};

so then you just store EventHandler pointers, and just call handler->handle_event(e) for each one when an event arrives.

Upvotes: 1

user319799
user319799

Reputation:

Sort of wasteful as it creates a dummy wrapper around each listener. However, I don't know if it is possible to cleanly convert std::function objects otherwise. If you accept only function pointers (but then lambdas are ruled out, meh) in addListener(), you could convert directly. Also, some reinterpret_cast could help, but I don't feel easy about that, so the solution doesn't feature one.

#include <functional>
#include <iostream>

struct IEvent { };
struct MouseEvent : IEvent { };

template <typename type>
std::function <void (IEvent*)>
convert_callback (const std::function <void (type*)>& callback)
{
  static_assert (std::is_convertible <type*, IEvent*>::value, "wrong type");
  return [=] (IEvent* event) { return callback (static_cast <MouseEvent*> (event)); };
}

struct dispatcher
{
  template <typename type>
  void
  addListener (std::function <void (type*)> callback)
  {
    std::function <void (IEvent*)>  real_callback (convert_callback (callback));
    MouseEvent  event;
    real_callback (&event);
  }
};

void
callback (MouseEvent*)
{
  std::cout << "hello\n";
}

int
main ()
{
  dispatcher x;
  x.addListener <MouseEvent> (&callback);
}

Any reason to pass event as pointer and not reference, btw?

Upvotes: 1

sonicwave
sonicwave

Reputation: 6092

I don't think that's possible, without changing the function parameter of addListener() to having a MouseEvent* parameter (or one derived from MouseEvent) instead of an IEvent* parameter.

If you have eventHandlers that take pointers to other classes (derived from IEvent) as a parameter, you'd thus need a separate addListener() function for each of these, which probably isn't what you want.

Simply passing an IEvent* and then casting it to the appropriate type in the eventHandler (as you're currently doing) is probably the way to go.

As an example, Qt actually does something something similar for its eventFilters: http://qt-project.org/doc/qt-4.8/eventsandfilters.html

Upvotes: 1

Related Questions