Introduction
The Event
class presented here is extremely useful for decoupling your classes from each other. Once you start using this class, all your code will naturally become event-driven. Event-driven programming has the advantage of making code highly expandable, readable and maintainable with no effort or extra lines of code. On the contrary, it allows you to achieve the same results with less code.
I might implement an event
class for Java too. However, the Java version would be a little bit different as without operator overloading support, I would have to define functions like addObserver
/removeObserver
.
Background
The class design is based on the observer design pattern.
The Design
I will take a top-down approach in explaining how an event
class can be designed. First, the interface we'd expect from a .NET like event is a class that can add/remove handlers and raise an event.
class Event
{
public:
void operator()(); Event& operator += (EventHandler* pHandlerToAdd); Event& operator -= (EventHandler* pHandlerTorRemove); };
Simple enough, but this design asks the user to create a middle object of type EventHandler
. It's better to provide the user with an automatic way with which he ONLY needs to supply his handler function. This is why we'll be adding a class with static
methods that create handlers from the user functions.
class EventHandler
{
public:
static EventHandler* Bind(void(*nonMemberFunctionToCall)());
};
Using these static
methods, now the user can add his functions as handlers without realizing an intermediate object is being created.
myEvent += EventHandler::Bind(myFunc);
Memory-wise, we can opt to use smart pointers or make the event
class own the memory of assigned handlers.
Now what's missing is that the user would want to have a member function as a handler. So, we'll add an overload of Bind()
for that purpose.
static EventHandler* Bind(void(ClassType::*memberFunctionToCall)(), ClassType* instance);
In C++, an instance of the class is needed in order to call a member function, but this is not the biggest of our problems. The big problem is that we can't create a version of Bind
function for each class we intend to use events with, or can we?
Actually we can, and that's by making the new Bind()
function a template function.
template<typename T> static EventHandler* Bind(void(T::*memberFunctionToCall)(), T* instance);
This would seem perfect, but as always, the devil is in the details; The EventHandler
created by Bind()
would need to save the passed pointer, which is of an unknown (template
) type.
One solution would be to make EventHandler
a template
class.
template<typename T> class EventHandler
{
public:
...
private:
T* instance;
void(T::*memberFunctiontoCall)();
};
But then operator +=
of Event
class won't know what to expect as its argument.
Yes, it is as you've guessed. The trivial solution of using inheritence. So the event
class would keep expecting a pointer to the base EventHandler
class while the Bind
function would create an inherited version.
template<typename T> class EventHandlerImpl: public EventHandler
{
public:
EventHandlerImpl(void(T::*memberFunctionToCall)(), T* instance); ...
};
class EventHandler
{
public:
template<typename T> static EventHandler* Bind(void(T::*memberFunctionToCall)(), T* instance)
{
return new EventHandlerImpl(memberFunctionToCall, instance);
}
...
Finally, to raise the event, some method must be called on that base EventHandler
class. This call has to be virtual as Bind()
would always create us an EventHandler
object of an inherited class.
class EventHandler
{
public:
...
virtual void RaiseEvent();
and then operator()
of class Event
would call that virtual
function through the pointer it keeps to each handler added.
The final version of the code was tuned a bit and is provided in the attached Event.h file.
Using the Code
When you design your app, you would first decide on your application-level components that perform the business logic. Each of these would have its own separate responsibilities and it would communicate with other components through events.
Shared functionality would be shared through making utility-level classes and injecting them to the application-level components (dependency injection). But this topic is out of the scope of this tip.
#include "Event.h" // This lib consists of only one file, just copy it and include it in your code.
class Cashier
{
public:
Sharp::Event<void> ShiftStarted; Sharp::Event<int> MoneyPaid;
void Start();
private:
void ProcessPayment(int amount)
{
MoneyPaid(amount); }
};
class Accountant
{
public:
void OnMoneyPaid(int& amount);
};
class Program
{
Cashier cashier1;
Accountant accountant1;
public:
Program()
{
cashier1.MoneyPaid += Sharp::EventHandler::Bind( &Accountant::OnMoneyPaid, &accountant1);
}
~Program()
{
cashier1.MoneyPaid -= Sharp::EventHandler::Bind( &Accountant::OnMoneyPaid, &accountant1);
}
void Start()
{
cashier1.Start();
}
};
void main()
{
Program theProgram;
theProgram.Start();
}
Build Notes
- This lib consists of only one file: "Event.h". Just copy it and include it in your code.
- You can define
SHARP_EVENT_NO_BOOST
in order to have no dependencies and manage thread-safety at the application level.
Points of Interest
- Event.h is well-documented in the hope that it would help someone out there learn more about templates,
virtual
functions and threading in general, as it provides a good example of mixing these technologies. - A note about the design: In C#, you would need to define a special version of
EventArgs
class in order to pass event data. While I understand why the language designers opted to do so, I'm not following the same strategy here and I'm relying on the C++ developer to be wise enough to limit the event class usage to passing actual events only.