C++ listenable event sources Part 1

Posted: July 31, 2008 in Computer and Internet

(Had to split this up in 2 posts, either Windows Live Writer or spaces is setting a limit)

We have been playing around with the same problem in Java lately. Trying to come up with a neat implementation technique for the listener pattern. today I was a bit intrigued at trying to solve this is C++ as such a class arose that needed a listener interface. Without anonymous classes in C++ this is a bit tricky (not sure anonymous classes are a good thing, but it makes it simpler for small things like the visit function, "closures" in C# 3.5 are very nice though). Speaking of C#, the delegate pattern already solves this and all concurrency problems  and is fully typed requiring zero crud coding so you can focus on the actual event. Don’t know if Java has anything similar.

Anyway, I tried to come up with a reusable class for the listener pattern, and now when I am done I am not sure which solution is better. This post is going to contain a lot of code so bear with me and feel free to advice should you happen to read this blog on the outer rim of the Internet. This could can be made more pluggable and also quite easily made concurrent friendly, but I let that out of the template for now (simple matter of 2 more template arguments to decide the container class and a "mutex" class, which could default to std::vector and NullMutex to get the current behavior).

   1: template<class Listener, typename Receiver, typename Context = int>
   2: class ListenerList
   3: {
   4: public:
   5:     typedef struct sListenerElem
   6:     {
   7:         Listener* listener;
   8:         void*     userParam;
   9:     } ListenerElem;
  10:  
  11:     typedef std::vector<ListenerElem> ListenerCollection;
  12:  
  13: public:
  14:     ListenerList(void) {};
  15:     ~ListenerList(void) {};
  16:  
  17:     typedef typename ListenerCollection::iterator iterator;
  18:  
  19:     iterator begin()
  20:     {
  21:         return m_listeners.begin();
  22:     }
  23:  
  24:     iterator end()
  25:     {
  26:         return m_listeners.end();
  27:     }
  28:  
  29:     int addListener(Listener* listener, void* userParam = NULL)
  30:     {
  31:         int res = -1;
  32:         
  33:         ListenerCollection::const_iterator iter = std::find_if(m_listeners.begin(), m_listeners.end(), ElementFinder(listener));
  34:         if (iter == m_listeners.end())
  35:         {
  36:             ListenerElem elem;
  37:             elem.listener   = listener;
  38:             elem.userParam  = userParam;
  39:  
  40:             m_listeners.push_back(elem);
  41:             res = m_listeners.size();
  42:         }
  43:  
  44:         return res;
  45:     }
  46:  
  47:     int removeListener(Listener* listener)
  48:     {
  49:         ListenerCollection::iterator iter;
  50:         int res = -1;
  51:  
  52:         iter = std::find_if(m_listeners.begin(), m_listeners.end(), ElementFinder(listener));
  53:         if (iter != m_listeners.end())
  54:         {
  55:             m_listeners.erase(iter);
  56:             res = m_listeners.size();
  57:         }
  58:         return res;
  59:     }
  60:  
  61:     typedef int (Receiver::*VisitorFn)(Listener& l, const Context& c, void* userParam);
  62:     int visit(Receiver& r, VisitorFn fn, const Context& c)
  63:     {
  64:         int i = 0;
  65:         for (ListenerCollection::iterator iter = m_listeners.begin(); iter != m_listeners.end(); iter++)
  66:         {
  67:             ListenerElem listener = (*iter);
  68:             (r.*fn)(*listener.listener, c, listener.userParam);
  69:             i++;
  70:         }
  71:         return i;
  72:     }
  73:  
  74: protected:
  75:     class ElementFinder
  76:     {
  77:     public:
  78:         ElementFinder(Listener* listener) { m_listener = listener;}; 
  79:         bool operator() (const ListenerElem& e) { return m_listener == e.listener; };
  80:     private:
  81:         Listener* m_listener;
  82:     };
  83:  
  84:     ListenerCollection        m_listeners;
  85: };

I am pretty happy with this class, except the ElementFinder class, but I am no stl wizard. With this class there are now at least two possible ways to continue. Either the event source itself aggregates this list and adds a addListener/removeListener and directs it towards the list. That’s straight forward and I like it. However in Java we fiddled with an inheritance, so I thought I would try it in C++ as well to see how it would turn out.

   1: template<class Listener, typename Context = int>
   2: class ListenableEventSource
   3: {
   4:  
   5: public:
   6:     ListenableEventSource(void) {};
   7:     virtual ~ListenableEventSource(void) {};
   8:  
   9:     int addListener(Listener* listener, void* userParam = NULL)
  10:     {
  11:         return m_listeners.addListener(listener, userParam);
  12:     }
  13:  
  14:     int removeListener(Listener* listener)
  15:     {
  16:         return m_listeners.removeListener(listener);
  17:     }
  18:  
  19: protected:
  20:     typedef ListenerList<Listener, ListenableEventSource, Context>        ListenerCollection;
  21:     
  22:     virtual int dispatchEvent(Listener& l, const Context& c, void* userParam) = 0;
  23:  
  24:     int fireNewEvent(const Context& c)
  25:     {
  26:         return m_listeners.visit(*this, &ListenableEventSource::dispatchEvent, c);
  27:     }
  28:  
  29:     ListenerCollection        m_listeners;
  30: };
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s