Click here to Skip to main content
15,891,431 members
Articles / Programming Languages / C#
Tip/Trick

A Mimetic C# Style Multicast Event Implementation with C++

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
26 Aug 2013CPOL 16.1K   11   6
A Mimetic C# Style Multicast Event Implementation with C++

Introduction

Although we have many ways to implement an observer pattern, inherited listener is used in most C++ OOP. Some programmers using C# (or Java, etc.) may be familiar with the non-inherited delegate solution, that is the most effective way in those programming languages. I'll show you a simple way to mime a C# style multicast event system with C++ in this tip. My implementation requires the fastdelegate library.

The Implementation

C++
#ifndef __EVENT_H__
#define __EVENT_H__
#include <set>
#include "FastDelegate.h"
#include "FastDelegateBind.h"
namespace kks {
/**
 * @brief Base class of event
 */
template<typename HandlerT>
class _Event : public std::set<fastdelegate::FastDelegate<HandlerT> > {
public:
    typedef std::set<fastdelegate::FastDelegate<HandlerT> > BaseType;
    typedef fastdelegate::FastDelegate<HandlerT> Handler;
public:
    /**
     * @brief Register an event handler
     *
     * @param[in] handler - Event handler delegate
     * @return - True if registeration succeed
     */
    bool operator += (const Handler &handler) {
        if(BaseType::find(handler) != BaseType::end())
          return false;
        BaseType::insert(handler);
        return true;
    }
    /**
     * @brief Unregister an event handler
     *
     * @param[in] handler - Event handler delegate
     * @return - True if unregisteration succeed
     */
    bool operator -= (const Handler &handler) {
        if(BaseType::find(handler) == BaseType::end())
          return false;
        BaseType::erase(handler);
        return true;
    }
};
/**
 * @brief Event class
 */
template<typename Signature>
class Event;
/**
 * @brief Event class without arguments
 * @note This is a final class which you cannot derive from it
 */
template<>
class Event<void(void)> : public _Event<void(void)> {
public:
    typedef _Event<void(void)> BaseType;
public:
    Event() {
    }
    Event(const Event::Handler &handler) {
        this->operator += (handler);
    }
    /**
     * @brief Raise an event
     */
    void operator() (void) const {
        for(const_iterator it = begin(); it != end(); ++it) {
          Handler d = *it;
          d();
        }
    }
};
/**
 * @brief Event class with 1 argument
 * @note This is a final class which you cannot derive from it
 */
template<typename Arg1T>
class Event<void(Arg1T)> : public _Event<void(Arg1T)> {
public:
    typedef _Event<void(Arg1T)> BaseType;
public:
    Event() {
    }
    Event(const typename Event::Handler &handler) {
        this->operator += (handler);
    }
    /**
     * @brief Raise an event
     *
     * @param[in] arg1 - 1st argument
     */
    void operator() (Arg1T &arg1) {
        for(typename BaseType::iterator it = BaseType::begin(); it != BaseType::end(); ++it) {
          typename BaseType::Handler d = *it;
          d(arg1);
        }
    }
    /**
     * @brief Raise an event
     *
     * @param[in] arg1 - 1st argument
     */
    void operator() (Arg1T arg1) const {
        for(typename BaseType::const_iterator it = BaseType::begin(); it != BaseType::end(); ++it) {
          typename BaseType::Handler d = *it;
          d(arg1);
        }
    }
};
/**
 * @brief Event class with 2 arguments
 * @note This is a final class which you cannot derive from it
 */
template<typename Arg1T, typename Arg2T>
class Event<void(Arg1T, Arg2T)> : public _Event<void(Arg1T, Arg2T)> {
public:
    typedef _Event<void(Arg1T, Arg2T)> BaseType;
public:
    Event() {
    }
    Event(const typename Event::Handler &handler) {
        this->operator += (handler);
    }
    /**
     * @brief Raise an event
     *
     * @param[in] arg1 - 1st argument
     * @param[in] arg2 - 2nd argument
     */
    void operator() (Arg1T &arg1, Arg2T &arg2) {
        for(typename BaseType::iterator it = BaseType::begin(); it != BaseType::end(); ++it) {
          typename BaseType::Handler d = *it;
          d(arg1, arg2);
        }
    }
    /**
     * @brief Raise an event
     *
     * @param[in] arg1 - 1st argument
     * @param[in] arg2 - 2nd argument
     */
    void operator() (Arg1T arg1, Arg2T arg2) const {
        for(typename BaseType::const_iterator it = BaseType::begin(); it != BaseType::end(); ++it) {
          typename BaseType::Handler d = *it;
          d(arg1, arg2);
        }
    }
};
/**
 * @brief Event class with 3 arguments
 * @note This is a final class which you cannot derive from it
 */
template<typename Arg1T, typename Arg2T, typename Arg3T>
class Event<void(Arg1T, Arg2T, Arg3T)> : public _Event<void(Arg1T, Arg2T, Arg3T)> {
public:
    typedef _Event<void(Arg1T, Arg2T, Arg3T)> BaseType;
public:
    Event() {
    }
    Event(const typename Event::Handler &handler) {
        this->operator += (handler);
    }
    /**
     * @brief Raise an event
     *
     * @param[in] arg1 - 1st argument
     * @param[in] arg2 - 2nd argument
     * @param[in] arg3 - 3rd argument
     */
    void operator() (Arg1T &arg1, Arg2T &arg2, Arg3T &arg3) {
        for(typename BaseType::iterator it = BaseType::begin(); it != BaseType::end(); ++it) {
          typename BaseType::Handler d = *it;
          d(arg1, arg2, arg3);
        }
    }
    /**
     * @brief Raise an event
     *
     * @param[in] arg1 - 1st argument
     * @param[in] arg2 - 2nd argument
     * @param[in] arg3 - 3rd argument
     */
    void operator() (Arg1T arg1, Arg2T arg2, Arg3T arg3) const {
        for(typename BaseType::const_iterator it = BaseType::begin(); it != BaseType::end(); ++it) {
          typename BaseType::Handler d = *it;
          d(arg1, arg2, arg3);
        }
    }
};
// Add more if you wish.
}
#endif // __EVENT_H__  

Using the Code

Follow these steps to use this page of code:

  1. You maybe should make an event type definition, because it eases further coding and maintaining:
    C#
    typedef Event<void(Arg1T, Arg2T, ...)> SomeEvent;
  2. You can use that definition to declare an event:
    C#
    SomeEvent OnSomeEvent;
  3. If something occurred, raise an event using:
    C#
    OnSomeEvent(Arg1, Arg2, ...);
  4. To register an event handler observing it, write
    C#
    obj->SomeEvent += SomeEvent::Handler(Class*, &Class::Method);
  5. Don't forget to unregister a handler after someone doesn't observe that event any more by:
    C#
    obj->SomeEvent -= SomeEvent::Handler(Class*, &Class::Method);
  6. A habitual pattern to define an event is some sort like:
    C#
    Event<void(Sender*, EventArgsStruct*)>
    in which "Sender" is an event raiser, whilst "EventArgsStruct" is a common structure which encapsulates detail event arguments; this is a usage derived from C#.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
China China
Video game player & creator; Hardware geek & maker.

Comments and Discussions

 
QuestionA slight different implementation Pin
Mizan Rahman30-Sep-13 1:38
Mizan Rahman30-Sep-13 1:38 
I managed to achieve two things using slight modified version of your solution.
1. Events can only be fired by the class that defines the events.
2. The list of the event handlers is accessible by the class that defines the event.

Here is the code:
C++
/*the template event*/
template<typename ownerT, typename signatureT>
  class CEvent /*: public std::set<fastdelegate::FastDelegate<HandlerT> >*/ 
  {
    friend ownerT;
  public:
    typedef fastdelegate::FastDelegate<signatureT> Handler;
    typedef std::set<Handler> BaseType;
  public:
    /**
    * @brief Register an event handler
    *
    * @param[in] handler - Event handler delegate
    * @return - True if registeration succeed
    */
    bool operator += (const Handler &handler) {
      if(events.find(handler) != events.end())
        return false;
      events.insert(handler);
      return true;
    }
    /**
    * @brief Unregister an event handler
    *
    * @param[in] handler - Event handler delegate
    * @return - True if unregisteration succeed
    */
    bool operator -= (const Handler &handler) {
      if(events.find(handler) == events.end())
        return false;
      events.erase(handler);
      return true;
    }
  private:
    BaseType events;
  };


/*the class that defines an event*/
class CMyClass
{
public:
 CMyClass()
 {

 }
 void FireMouseClick(CPoint pt);
 {
  OnMouseClick(pt);
 }
 protected:
  // if overidden, base method must be called to fire the events.
  // Notice this is accessing the private member "events" of the CEvent class.
  // Because this class is now friend of CEvent
  virtual void OnMouseClick(CPoint pt);
  {
    bool bHandled = false;
    for(CMouseClick::BaseType::const_iterator it = OnMouseClickEvent.events.begin(); it != OnMouseClickEvent.events.end(); ++it)
    {
      CMouseClick::Handler d = *it;
      d(this, pt, bHandled);
      if (bHandled)
      {
        break;
      }
    }
  }
  

 private: // events
    typedef CEvent<CMyClass, void(const CMyClass * pSender, CPoint point, bool & bHandled)> CMouseClick;
 public:
   CMouseClick OnMouseClickEvent;

};

/* an event handler */
void ClickHandler(const CMyClass * pSender, CPoint point, bool & bHandled)
{
  printf("Mouse clicked fired: %d, %d", point.x point.y);
};

/*user of an event*/
main()
{
  CMyClass cls;
  cls.OnMouseClickevent += ClickHandler;
  cls.FireMouseClick(CPoint(2, 3));
};

QuestionNice.. Pin
Mizan Rahman2-Sep-13 3:37
Mizan Rahman2-Sep-13 3:37 
AnswerRe: Nice.. Pin
paladin_t2-Sep-13 23:55
paladin_t2-Sep-13 23:55 
GeneralRe: Nice.. Pin
Mizan Rahman23-Sep-13 21:53
Mizan Rahman23-Sep-13 21:53 
QuestionVery nice! Pin
CDP180227-Aug-13 19:34
CDP180227-Aug-13 19:34 
AnswerRe: Very nice! Pin
paladin_t28-Aug-13 0:40
paladin_t28-Aug-13 0:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.