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);});