Reputation: 621
I've been learning C++ and trying to implement various idioms from other languages I'm familiar with.
Recently I've tried to implement a central EventDispatcher which multiple objects can register events that can take N number of arguments, and a single call to EventDispatcher::dispatch(event_name) would call all registered event callbacks.
First I created a template class for Event;
template <typename ..._args>
class Event
{
public:
//Alias for template callback
using _callback = std::function<void(_args...)>;
//Ctor & Dtor
explicit Event(std::string const& name, _callback const& cb) : _name(name), _cbFunc(cb) {}
~Event() {}
//Accessors
std::string const& getName() const { return this->_name; }
//Methods
void trigger(_args... a) { this->_cbFunc(a...); }
private:
//Event identifier
std::string _name;
//Event callback - can't be changed inside event.
_callback const _cbFunc;
};
With this structure I can define events like in the following example:
void testCallback(int a) {
std::cout << a << std::endl;
}
Event<int> _event("TestEvent", std::bind(&testCallback, _1));
_event.trigger(20); //This will actually output 20 to the console.
What I want to do next is, have an EventDispatcher class which should be defined like:
class EventDispatcher
{
public:
EventDispatcher();
~EventDispatcher();
void registerEvent(**Event of any type**);
void dispatchEvent(std::string const& eventName, ....args);
private:
std::map<std::string, std::vector<**Event of any type**>> _eventList;
};
so that I can make calls like this;
EventDispatcher _dispatcher;
_dispatcher.registerEvent(_event); //event defined in the previous example.
_dispatcher.dispatchEvent("TestEvent", 20); //this should call event's trigger() method with arguments passed to it actually.
However I couldn't figure out how to implement a central dispatcher and have it be able to register templatized event instances, and then pass variable number of arguments to dispatchEvent(...) and make it trigger all events in a vector of templatized events.
How can I achieve such functionality? Is my thought proccess correct or far from c++ way of implementing such a system? Any tips would be welcome.
Upvotes: 4
Views: 4483
Reputation: 211007
I suggest a solution with an abstract base class (IEvent
) for the event class (Event
)
and a template dispatcher method with variadic arguments and a dynamic_cast
.
Apstrac base class with the abstract method getName
:
class IEvent
{
public:
virtual const std::string & getName() const = 0;
};
Your event class derived from IEvent
:
template <typename ..._args>
class Event : public IEvent
{
public:
//Alias for template callback
using _callback = std::function<void(_args...)>;
//Ctor & Dtor
//template< typename T_CB >
explicit Event( const std::string & name, const _callback & cb ) : _name(name), _cbFunc(cb) {}
~Event() {}
//Accessors
virtual const std::string & getName() const override { return this->_name; }
//Methods
void trigger(_args... a) { this->_cbFunc(a...); }
private:
//Event identifier
std::string _name;
//Event callback - can't be changed inside event.
_callback const _cbFunc;
};
The dispatcher with the template method:
class EventDispatcher
{
public:
EventDispatcher() {}
~EventDispatcher()
{
for ( auto el : _eventList )
{
for ( auto e : el.second )
delete e;
}
}
void registerEvent( IEvent *event )
{
if ( event )
_eventList[event->getName()].push_back( event );
}
template <typename ..._args>
void dispatchEvent( const std::string & eventName, _args...a )
{
auto it_eventList = _eventList.find( eventName );
if ( it_eventList == _eventList.end() )
return;
for ( auto ie : it_eventList->second )
{
if ( Event<_args...> * event = dynamic_cast<Event<_args...>*>( ie ) )
event->trigger( a... );
}
}
private:
std::map<std::string, std::vector<IEvent*>> _eventList;
};
And finally the application:
void testCallback(int a) {
std::cout << a << std::endl;
}
int main()
{
EventDispatcher eventDisp;
eventDisp.registerEvent( new Event<int>( "TestEvent", &testCallback ) );
eventDisp.dispatchEvent( "TestEvent", 20 );
return 0;
}
Upvotes: 5