Sharp Tools: A .NET like Event in C++






4.81/5 (18 votes)
This tip presents a .NET like Event implemented in C++, with which developers can fire events in C++ the same way they do in C#.
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()(); // to be used to raise the event
Event& operator += (EventHandler* pHandlerToAdd); // to be used to add event handlers
Event& operator -= (EventHandler* pHandlerTorRemove); // to be used to remove event handlers
};
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); // sample usage of the new interface
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.
// showing a wrong way to solve the issue
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.
//Event& operator += (EventHandler< ??? >* pHandlerToAdd);
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.
// An inherited version of EventHandler
template<typename T> class EventHandlerImpl: public EventHandler
{
public:
EventHandlerImpl(void(T::*memberFunctionToCall)(), T* instance); // constructor
...
};
// showing how the Bind function would create an inherited version depending on passed arguments type
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.
// an example of an application-level component which perform part of the business logic
class Cashier
{
public:
Sharp::Event<void> ShiftStarted; // an event that pass no argument when raised
Sharp::Event<int> MoneyPaid; // will be raised whenever the cashier receives money
void Start(); // called on startup, perform business logic and eventually calls ProcessPayment()
private:
// somewhere along the logic of this class
void ProcessPayment(int amount)
{
// after some processing
MoneyPaid(amount); // this how we raise the event
}
};
// Another application-level component
class Accountant
{
public:
void OnMoneyPaid(int& amount);
};
// The main class that decide on the events flow (who handles which events)
// it is also the owner of the application-level components
class Program
{
// the utility level components(if any)
//(commented)DataStore databaseConnection;
// the application-level components
Cashier cashier1;
Accountant accountant1;
//(commented)AttendanceManager attMan(&databaseConnection) // an example of injecting a utility object
public:
Program()
{
// connect the events of the application-level components to their handlers
cashier1.MoneyPaid += Sharp::EventHandler::Bind( &Accountant::OnMoneyPaid, &accountant1);
}
~Program()
{
// it is recommended to always connect the event handlers in the constructor
// and disconnect in the destructor
cashier1.MoneyPaid -= Sharp::EventHandler::Bind( &Accountant::OnMoneyPaid, &accountant1);
}
void Start()
{
// start business logic, maybe tell all cashiers to start their shift
cashier1.Start();
}
};
void main()
{
Program theProgram;
theProgram.Start();
// as you can see the Cashier and the Accountant know nothing about each other
// You can even remove the Accountant class without affecting the system
// You can add new components (ex: AttendanceManager) without affecting the system
// all you need to change is the part where you connect/disconnect the events
}
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.