Since I love progamming in both C# and C++, I'm about to implementing a C#-like event system as a solid base for my planned C++ SFML-GUI.
This is only an excerpt of my code and I hope this clarifies my concept:
// Event.h
// STL headers:
#include <functional>
#include <type_traits>
#include <iostream>
// boost headers:
#include <boost/signals/trackable.hpp>
#include <boost/signal.hpp>
namespace Utils
{
namespace Gui
{
#define IMPLEMENTS_EVENT(EVENTNAME, EVENTARGS) public: \
Utils::Gui::IEvent<EVENTARGS>& EVENTNAME() { return m_on##EVENTNAME; } \
protected: \
virtual void On##EVENTNAME(EVENTARGS& e) { m_on##EVENTNAME(this, e); } \
private: \
Utils::Gui::Event<EVENTARGS> m_on##EVENTNAME;
#define MAKE_EVENTFIRING_CLASS(EVENTNAME, EVENTARGS) class Fires##EVENTNAME##Event \
{ \
IMPLEMENTS_EVENT(EVENTNAME, EVENTARGS); \
};
class EventArgs
{
public:
static EventArgs Empty;
};
EventArgs EventArgs::Empty = EventArgs();
template<class TEventArgs>
class EventHandler : public std::function<void (void*, TEventArgs&)>
{
static_assert(std::is_base_of<EventArgs, TEventArgs>::value,
"EventHandler must be instantiated with a TEventArgs template paramater type deriving from EventArgs.");
public:
typedef void Signature(void*, TEventArgs&);
typedef void (*HandlerPtr)(void*, TEventArgs&);
EventHandler() : std::function<Signature>() { }
template<class TContravariantEventArgs>
EventHandler(const EventHandler<TContravariantEventArgs>& rhs)
: std::function<Signature>(reinterpret_cast<HandlerPtr>(*rhs.target<EventHandler<TContravariantEventArgs>::HandlerPtr>()))
{
static_assert(std::is_base_of<TContravariantEventArgs, TEventArgs>::value,
"The eventHandler instance to copy does not suffice the rules of contravariance.");
}
template<class F>
EventHandler(F f) : std::function<Signature>(f) { }
template<class F, class Allocator>
EventHandler(F f, Allocator alloc) : std::function<Signature>(f, alloc) { }
};
template<class TEventArgs>
class IEvent
{
public:
typedef boost::signal<void (void*, TEventArgs&)> SignalType;
void operator+= (const EventHandler<TEventArgs>& eventHandler)
{
Subscribe(eventHandler);
}
void operator-= (const EventHandler<TEventArgs>& eventHandler)
{
Unsubscribe(eventHandler);
}
virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler) = 0;
virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler, int group) = 0;
virtual void Unsubscribe(const EventHandler<TEventArgs>& eventHandler) = 0;
};
template<class TEventArgs>
class Event : public IEvent<TEventArgs>
{
public:
virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler)
{
m_signal.connect(*eventHandler.target<EventHandler<TEventArgs>::HandlerPtr>());
}
virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler, int group)
{
m_signal.connect(group, *eventHandler.target<EventHandler<TEventArgs>::HandlerPtr>());
}
virtual void Unsubscribe(const EventHandler<TEventArgs>& eventHandler)
{
m_signal.disconnect(*eventHandler.target<EventHandler<TEventArgs>::HandlerPtr>());
}
void operator() (void* sender, TEventArgs& e)
{
m_signal(sender, e);
}
private:
SignalType m_signal;
};
class IEventListener : public boost::signals::trackable
{
};
};
};
As you can see, I'm using boost::signal as my actual event system, but I encapsulate it with the IEvent interface (which is actually an abstract class) to prevent event listeners to fire the event via operator().
For convenience I overloaded the add-assignment and subtract-assignment operators. If I do now derive my event listening classes from IEventListener, I am able to write code without needing to worry about dangling function pointer in the signal.
So far I'm testing my results, but I have trouble with std::tr1::function::target<TFuncPtr>()
:
class BaseEventArgs : public Utils::Gui::EventArgs
{
};
class DerivedEventArgs : public BaseEventArgs
{
};
void Event_BaseEventRaised(void* sender, BaseEventArgs& e)
{
std::cout << "Event_BaseEventRaised called";
}
void Event_DerivedEventRaised(void* sender, DerivedEventArgs& e)
{
std::cout << "Event_DerivedEventRaised called";
}
int main()
{
using namespace Utils::Gui;
typedef EventHandler<BaseEventArgs>::HandlerPtr pfnBaseEventHandler;
typedef EventHandler<DerivedEventArgs>::HandlerPtr pfnNewEventHandler;
// BaseEventHandler with a function taking a BaseEventArgs
EventHandler<BaseEventArgs> baseEventHandler(Event_BaseEventRaised);
// DerivedEventHandler with a function taking a DerivedEventArgs
EventHandler<DerivedEventArgs> newEventHandler(Event_DerivedEventRaised);
// DerivedEventHandler with a function taking a BaseEventArgs -> Covariance
EventHandler<DerivedEventArgs> covariantBaseEventHandler(Event_BaseEventRaised);
const pfnBaseEventHandler* pBaseFunc = baseEventHandler.target<pfnBaseEventHandler>();
std::cout << "baseEventHandler function pointer is " << ((pBaseFunc != nullptr) ? "valid" : "invalid") << std::endl;
const pfnNewEventHandler* pNewFunc = newEventHandler.target<pfnNewEventHandler>();
std::cout << "baseEventHandler function pointer is " << ((pNewFunc != nullptr) ? "valid" : "invalid") << std::endl;
// Here is the error, covariantBaseEventHandler actually stores a pfnBaseEventHandler:
pNewFunc = covariantBaseEventHandler.target<pfnNewEventHandler>();
std::cout << "covariantBaseEventHandler as pfnNewEventHandler function pointer is " << ((pNewFunc != nullptr) ? "valid" : "invalid") << std::endl;
// This works as expected, but template forces compile-time knowledge of the function pointer type
pBaseFunc = covariantBaseEventHandler.target<pfnBaseEventHandler>();
std::cout << "covariantBaseEventHandler as pfnBaseEventHandler function pointer is " << ((pBaseFunc != nullptr) ? "valid" : "invalid") << std::endl;
return EXIT_SUCCESS;
}
The EventHandler<TEventArgs>::target<TFuncPtr>()
method will only return a valid pointer if TFuncPtr is the exact same type as stored in the Functor, regardless of covariance.
Because of the RTTI check, it prohibits to access the pointer as a standard weakly-typed C function pointer, which is kind of annoying in cases like this one.
The EventHandler is of type DerivedEventArgs but nevertheless points to a pfnBaseEventHandler function even though the function ran through the constructor.
That means, that std::tr1::function itself "supports" contravariance, but I can't find a way of simply getting the function pointer out of the std::tr1::funcion object if I don't know its type at compile time which is required for a template argument.
I would appreciate in cases like this that they added a simple get() method like they did for RAII pointer types.
Since I'm quite new to programming, I would like to know if there is a way to solve this problem, preferrably at compile-time via templates (which I think would be the only way).