I am working on a simple event mechanism for c++. Since Iam not very experienced I would like to share my code in order to get some thoughts.
I also dont like the BIND_X macros. Ideas how to simplify the binding process are welcome as well any comments regarding problems, bad style...
#ifndef EVENTS_H
#define EVENTS_H
#include <functional>
#include <algorithm>
#include <random>
#include <limits>
#include <string>
#include <forward_list>
#include <mutex>
namespace event {
// The type of the source identifier passed to each event listener
typedef const std::string& source_t;
///
/// \brief A handle that identifiers listeners.
///
/// \details Listeners are functions that get called once a event is fired.
/// This struct is used to identify such functions and is used to detach them.
///
struct listener_handle
{
public:
///
/// \brief Create a new handle
/// \param s The source
/// \param h The handle id
///
listener_handle(source_t s="", int h=0) :
source(s),
handle(h)
{ }
///
/// \brief Equals operator
/// \param other The handle to compare
/// \return True, if the handles are equal
///
bool operator==(const listener_handle& other) const
{
return this->source == other.source &&
this->handle == other.handle;
}
std::string source;
int handle;
};
template <class... T>
///
/// \brief The event class.
///
class Event
{
public:
typedef std::function<void(source_t, T...)> func;
///
/// \brief Create new instance
/// \param source The name of the event source.
///
Event(source_t source) :
source(source)
{}
///
/// \brief Release resources
///
virtual ~Event()
{
this->listeners.clear();
}
///
/// \brief Attach an event
/// \param newListener The event listener to attach
/// \return The handle that may be used to detach the event
///
virtual listener_handle& Attach(const func& newListener)
{
this->listeners.push_front(Listener{newListener, this->createListenerHandle()});
return this->listeners.front().handle;
}
///
/// \brief Detach an event using its id
/// \param id The id of the event to detach
///
virtual void Detach(const listener_handle& handle)
{
this->listeners.remove_if([handle] (const Listener& l) {return l.handle == handle;});
}
///
/// \brief Call all listeners
/// \param argument The EventArgs to send
///
virtual void Invoke(const T&... args) const
{
std::for_each(std::begin(this->listeners), std::end(this->listeners), [this, &args...] (const Listener& l) {
l.listener(this->source, args...);
});
}
private:
struct Listener {
func listener;
listener_handle handle;
};
///
/// \brief Create a random number using per thread local seed.
/// \return A random number between int min and int max
///
int createRandom() const
{
static std::mt19937 gen{std::random_device{}()};
static std::uniform_int_distribution<> dist{
std::numeric_limits<int>::min(),
std::numeric_limits<int>::max()};
return dist(gen);
}
///
/// \brief Create a new listener handle using the registered source name
/// \return A new listener handle
///
listener_handle createListenerHandle() const
{
return listener_handle{this->source, this->createRandom()};
}
std::string source;
std::forward_list<Listener> listeners;
};
template <typename... T>
///
/// \brief The thread safe event class.
///
/// \details This class should be used if the exposed event may be accessed from multiple threads.
///
class TsEvent : public Event<T...>
{
public:
///
/// \copydoc Event::Event()
///
TsEvent(source_t source): Event<T...>(source)
{ }
///
/// \copydoc Event::~Event()
///
virtual ~TsEvent()
{ }
///
/// \copydoc Event::Attach()
///
virtual listener_handle& Attach(const typename Event<T...>::func& newListener) override
{
std::lock_guard<std::mutex> lg(this->m);
return Event<T...>::Attach(newListener);
}
///
/// \copydoc Event::Detach()
///
virtual void Detach(const listener_handle& handle) override
{
std::lock_guard<std::mutex> lg(this->m);
return Event<T...>::Detach(handle);
}
///
/// \copydoc Event::Invoke()
///
virtual void Invoke(const T&... args) const override
{
std::lock_guard<std::mutex> lg(this->m);
return Event<T...>::Invoke(args...);
}
private:
std::mutex m;
};
} //event namespace
#endif // EVENTS_H
Sample usage (assume CallMe is a static function with 2 parameter):
#include <string>
#include <iostream>
#include "event.hpp"
void CallMe(std::string s, int i) {
std::cout << s << "-" << i << '\n';
}
int main() {
auto t = new event::Event<int>{"Basic"};
auto handle = t->Attach(std::function<void(std::string, int)>{CallMe});
t->Invoke(5);
t->Detach(handle);
delete t;
}
Note: The bind macros are used to simplify binding of methods that require some kind of instance so call. Assuming a class s exposes an event e that requires 3 parameter and callMe (defined inside receiver) satisfies this, one may use it like that:
auto handle = s->e->Attach(BIND_3(receiver::callMe, r));
Edit: Here is an example of how to avoid the BIND_X macros (and that is why i got rid if them)
auto handle = s->e->Attach([r](const std::string& s, int a, int b) {r->callMe(s, a, b);});