Event (Signal) Klasse

04/11/2013 16:11 Master674b#1
Hey, hatte mir als alternative zu boost::signal (da ich die irgendwie unbequem finde) eine eigene Event Klasse zusammengebaut.

Das ganze setzt voraus, dass euer Compiler variadische Templates unterstützt, ansonsten könnt ihr die Klasse mit mehreren Template Spezialisierungen überladen (hab da momentan 4 Überladungen drin, mehr Argumente brauch ich eigentlich nie).

Naja sieht jedenfalls so aus:

Code:
namespace Utils
{
	struct Minimum {
		template <typename T, typename InputIterator>
		static T Comp(InputIterator first, InputIterator last) {
			if (first == last)
				return T();

			T min_value = *first++;
			while (first != last) {
				if (min_value > *first)
					min_value = *first;
				++first;
			}

			return min_value;
		}
	};

	struct Maximum {
		template <typename T, typename InputIterator>
		static T Comp(InputIterator first, InputIterator last) {
			if (first == last)
				return T();

			T max_value = *first++;
			while (first != last) {
				if (max_value < *first)
					max_value = *first;
				++first;
			}

			return max_value;
		}
	};

	template <typename FuncT>
	struct SEventDelegate {
		uint64 token;
		std::function<FuncT> callback;
	};

	template <typename FuncT>
	class Event {};

#ifdef VARIADIC_TEMPLATES_SUPPORTED
	// Threadsafe
	template <typename R, typename... Args>
	class Event<R (Args...)>
	{
	public:
		typedef R fnconv_t(Args...);
		Event() : m_mutex(), m_tokenMgr(), m_delegates() {}

		Event(const std::function<fnconv_t> &func) :
			m_mutex(), m_tokenMgr(), m_delegates() {
			connect(func);
		}

		Event(Event<fnconv_t> &&other) :
			m_tokenMgr(std::move(other.m_tokenMgr)),
			m_mutex(std::move(other.m_mutex)),
			m_delegates(std::move(other.m_delegates)) {}

		Event<fnconv_t>& operator=(Event<fnconv_t> &&other) {
			m_tokenMgr = std::move(other.m_tokenMgr);
			m_mutex = std::move(other.m_mutex);
			m_delegates = std::move(other.m_delegates);
			return *this;
		}

		R operator()(const Args&... args) const {
			return _invoke_impl<R, Maximum>(args...);
		}

		template <typename CompT>
		R invoke(const Args&... args) const {
			return _invoke_impl<R, CompT>(args...);
		}

		SEventDelegate<fnconv_t> operator+=(const std::function<fnconv_t> &func) {
			return connect(func);
		}

		Event<fnconv_t>& operator-=(const SEventDelegate<fnconv_t> &deleg) {
			remove(deleg.token);
			return *this;
		}

		SEventDelegate<fnconv_t> connect(const std::function<fnconv_t> &func) {
			std::lock_guard<std::mutex> g(m_mutex);
			SEventDelegate<fnconv_t> deleg = { m_tokenMgr.get(), func };
			m_delegates[deleg.token] = deleg;
			return deleg;
		}

		void remove(const uint64 &token) {
			std::lock_guard<std::mutex> g(m_mutex);
			auto itr = m_delegates.find(token);
			if (itr != m_delegates.end()) {
				m_tokenMgr.remove(token);
				m_delegates.erase(itr);
			}
		}

		void clear() {
			m_mutex.lock();
			m_delegates.clear();
			m_tokenMgr.reset();
			m_mutex.unlock();
		}

	protected:
		TokenManager m_tokenMgr;
		mutable std::mutex m_mutex;
		std::map<uint64, SEventDelegate<fnconv_t>> m_delegates;

		template <typename T, typename CompT>
		typename std::enable_if<std::is_same<void, T>::value, T>::type
			_invoke_impl(const Args&... args) const {
			m_mutex.lock();
			auto mDelegates = m_delegates;
			m_mutex.unlock();

			for (auto& pair: mDelegates)
				pair.second.callback(args...);
		}

		template <typename T, typename CompT>
		typename std::enable_if<!std::is_same<void, T>::value, T>::type
			_invoke_impl(const Args&... args) const {
			m_mutex.lock();
			auto mDelegates = m_delegates;
			m_mutex.unlock();

			std::vector<T> results;
			results.reserve(mDelegates.size());
			for (auto& pair: mDelegates)
				results.push_back(pair.second.callback(args...));
			return CompT::Comp<T>(results.cbegin(), results.cend());
		}
	};
#else
	// [...] kein Variadic Template Support
	// Overloads von partiellen Template-Spezialisierungen benutzen.

	//////////// EVENT P0 //////////////

	// Threadsafe
	template <typename R>
	class Event<R (void)>
	{
	public:
		typedef R fnconv_t(void);
		Event() : m_mutex(), m_tokenMgr(), m_delegates() {}

		Event(const std::function<fnconv_t> &func) :
			m_mutex(), m_tokenMgr(), m_delegates() {
			connect(func);
		}

		Event(Event<fnconv_t> &&other) :
			m_tokenMgr(std::move(other.m_tokenMgr)),
			m_mutex(std::move(other.m_mutex)),
			m_delegates(std::move(other.m_delegates)) {}

		Event<fnconv_t>& operator=(Event<fnconv_t> &&other) {
			m_tokenMgr = std::move(other.m_tokenMgr);
			m_mutex = std::move(other.m_mutex);
			m_delegates = std::move(other.m_delegates);
			return *this;
		}

		R operator()() const {
			return _invoke_impl<R, Maximum>();
		}

		template <typename CompT>
		R invoke() const {
			return _invoke_impl<R, CompT>();
		}

		SEventDelegate<fnconv_t> operator+=(const std::function<fnconv_t> &func) {
			return connect(func);
		}

		Event<fnconv_t>& operator-=(const SEventDelegate<fnconv_t> &deleg) {
			remove(deleg.token);
			return *this;
		}

		SEventDelegate<fnconv_t> connect(const std::function<fnconv_t> &func) {
			std::lock_guard<std::mutex> g(m_mutex);
			SEventDelegate<fnconv_t> deleg = { m_tokenMgr.get(), func };
			m_delegates[deleg.token] = deleg;
			return deleg;
		}

		void remove(const uint64 &token) {
			std::lock_guard<std::mutex> g(m_mutex);
			auto itr = m_delegates.find(token);
			if (itr != m_delegates.end()) {
				m_tokenMgr.remove(token);
				m_delegates.erase(itr);
			}
		}

		void clear() {
			m_mutex.lock();
			m_delegates.clear();
			m_tokenMgr.reset();
			m_mutex.unlock();
		}

	protected:
		TokenManager m_tokenMgr;
		mutable std::mutex m_mutex;
		std::map<uint64, SEventDelegate<fnconv_t>> m_delegates;

		template <typename T, typename CompT>
		typename std::enable_if<std::is_same<void, T>::value, T>::type
			_invoke_impl() const {
			m_mutex.lock();
			auto mDelegates = m_delegates;
			m_mutex.unlock();

			for (auto& pair: mDelegates)
				pair.second.callback();
		}

		template <typename T, typename CompT>
		typename std::enable_if<!std::is_same<void, T>::value, T>::type
			_invoke_impl() const {
			m_mutex.lock();
			auto mDelegates = m_delegates;
			m_mutex.unlock();

			std::vector<T> results;
			results.reserve(mDelegates.size());
			for (auto& pair: mDelegates)
				results.push_back(pair.second.callback());
			return CompT::Comp<T>(results.cbegin(), results.cend());
		}
	};

	//////////// EVENT P1 //////////////

	// Threadsafe
	template <typename R, typename A0>
	class Event<R (A0)>
	{
	public:
		typedef R fnconv_t(A0);
		Event() : m_mutex(), m_tokenMgr(), m_delegates() {}

		Event(const std::function<fnconv_t> &func) :
			m_mutex(), m_tokenMgr(), m_delegates() {
			connect(func);
		}

		Event(Event<fnconv_t> &&other) :
			m_tokenMgr(std::move(other.m_tokenMgr)),
			m_mutex(std::move(other.m_mutex)),
			m_delegates(std::move(other.m_delegates)) {}

		Event<fnconv_t>& operator=(Event<fnconv_t> &&other) {
			m_tokenMgr = std::move(other.m_tokenMgr);
			m_mutex = std::move(other.m_mutex);
			m_delegates = std::move(other.m_delegates);
			return *this;
		}

		R operator()(const A0 &a0) const {
			return _invoke_impl<R, Maximum>(a0);
		}

		template <typename CompT>
		R invoke(const A0 &a0) const {
			return _invoke_impl<R, CompT>(a0);
		}

		SEventDelegate<fnconv_t> operator+=(const std::function<fnconv_t> &func) {
			return connect(func);
		}

		Event<fnconv_t>& operator-=(const SEventDelegate<fnconv_t> &deleg) {
			remove(deleg.token);
			return *this;
		}

		SEventDelegate<fnconv_t> connect(const std::function<fnconv_t> &func) {
			std::lock_guard<std::mutex> g(m_mutex);
			SEventDelegate<fnconv_t> deleg = { m_tokenMgr.get(), func };
			m_delegates[deleg.token] = deleg;
			return deleg;
		}

		void remove(const uint64 &token) {
			std::lock_guard<std::mutex> g(m_mutex);
			auto itr = m_delegates.find(token);
			if (itr != m_delegates.end()) {
				m_tokenMgr.remove(token);
				m_delegates.erase(itr);
			}
		}

		void clear() {
			m_mutex.lock();
			m_delegates.clear();
			m_tokenMgr.reset();
			m_mutex.unlock();
		}

	protected:
		TokenManager m_tokenMgr;
		mutable std::mutex m_mutex;
		std::map<uint64, SEventDelegate<fnconv_t>> m_delegates;

		template <typename T, typename CompT>
		typename std::enable_if<std::is_same<void, T>::value, T>::type
			_invoke_impl(const A0 &a0) const {
			m_mutex.lock();
			auto mDelegates = m_delegates;
			m_mutex.unlock();

			for (auto& pair: mDelegates)
				pair.second.callback(a0);
		}

		template <typename T, typename CompT>
		typename std::enable_if<!std::is_same<void, T>::value, T>::type
			_invoke_impl(const A0 &a0) const {
			m_mutex.lock();
			auto mDelegates = m_delegates;
			m_mutex.unlock();

			std::vector<T> results;
			results.reserve(mDelegates.size());
			for (auto& pair: mDelegates)
				results.push_back(pair.second.callback(a0));
			return CompT::Comp<T>(results.cbegin(), results.cend());
		}
	};

	//////////// EVENT P2 //////////////

	// Threadsafe
	template <typename R, typename A0, typename A1>
	class Event<R (A0, A1)>
	{
	public:
		typedef R fnconv_t(A0, A1);
		Event() : m_mutex(), m_tokenMgr(), m_delegates() {}

		Event(const std::function<fnconv_t> &func) :
			m_mutex(), m_tokenMgr(), m_delegates() {
			connect(func);
		}

		Event(Event<fnconv_t> &&other) :
			m_tokenMgr(std::move(other.m_tokenMgr)),
			m_mutex(std::move(other.m_mutex)),
			m_delegates(std::move(other.m_delegates)) {}

		Event<fnconv_t>& operator=(Event<fnconv_t> &&other) {
			m_tokenMgr = std::move(other.m_tokenMgr);
			m_mutex = std::move(other.m_mutex);
			m_delegates = std::move(other.m_delegates);
			return *this;
		}

		R operator()(const A0 &a0, const A1 &a1) const {
			return _invoke_impl<R, Maximum>(a0, a1);
		}

		template <typename CompT>
		R invoke(const A0 &a0, const A1 &a1) const {
			return _invoke_impl<R, CompT>(a0, a1);
		}

		SEventDelegate<fnconv_t> operator+=(const std::function<fnconv_t> &func) {
			return connect(func);
		}

		Event<fnconv_t>& operator-=(const SEventDelegate<fnconv_t> &deleg) {
			remove(deleg.token);
			return *this;
		}

		SEventDelegate<fnconv_t> connect(const std::function<fnconv_t> &func) {
			std::lock_guard<std::mutex> g(m_mutex);
			SEventDelegate<fnconv_t> deleg = { m_tokenMgr.get(), func };
			m_delegates[deleg.token] = deleg;
			return deleg;
		}

		void remove(const uint64 &token) {
			std::lock_guard<std::mutex> g(m_mutex);
			auto itr = m_delegates.find(token);
			if (itr != m_delegates.end()) {
				m_tokenMgr.remove(token);
				m_delegates.erase(itr);
			}
		}

		void clear() {
			m_mutex.lock();
			m_delegates.clear();
			m_tokenMgr.reset();
			m_mutex.unlock();
		}

	protected:
		TokenManager m_tokenMgr;
		mutable std::mutex m_mutex;
		std::map<uint64, SEventDelegate<fnconv_t>> m_delegates;

		template <typename T, typename CompT>
		typename std::enable_if<std::is_same<void, T>::value, T>::type
			_invoke_impl(const A0 &a0, const A1 &a1) const {
			m_mutex.lock();
			auto mDelegates = m_delegates;
			m_mutex.unlock();

			for (auto& pair: mDelegates)
				pair.second.callback(a0, a1);
		}

		template <typename T, typename CompT>
		typename std::enable_if<!std::is_same<void, T>::value, T>::type
			_invoke_impl(const A0 &a0, const A1 &a1) const {
			m_mutex.lock();
			auto mDelegates = m_delegates;
			m_mutex.unlock();

			std::vector<T> results;
			results.reserve(mDelegates.size());
			for (auto& pair: mDelegates)
				results.push_back(pair.second.callback(a0, a1));
			return CompT::Comp<T>(results.cbegin(), results.cend());
		}
	};

	//////////// EVENT P3 //////////////

	// Threadsafe
	template <typename R, typename A0, typename A1, typename A2>
	class Event<R (A0, A1, A2)>
	{
	public:
		typedef R fnconv_t(A0, A1, A2);
		Event() : m_mutex(), m_tokenMgr(), m_delegates() {}

		Event(const std::function<fnconv_t> &func) :
			m_mutex(), m_tokenMgr(), m_delegates() {
			connect(func);
		}

		Event(Event<fnconv_t> &&other) :
			m_tokenMgr(std::move(other.m_tokenMgr)),
			m_mutex(std::move(other.m_mutex)),
			m_delegates(std::move(other.m_delegates)) {}

		Event<fnconv_t>& operator=(Event<fnconv_t> &&other) {
			m_tokenMgr = std::move(other.m_tokenMgr);
			m_mutex = std::move(other.m_mutex);
			m_delegates = std::move(other.m_delegates);
			return *this;
		}

		R operator()(const A0 &a0, const A1 &a1, const A2 &a2) const {
			return _invoke_impl<R, Maximum>(a0, a1, a2);
		}

		template <typename CompT>
		R invoke(const A0 &a0, const A1 &a1, const A2 &a2) const {
			return _invoke_impl<R, CompT>(a0, a1, a2);
		}

		SEventDelegate<fnconv_t> operator+=(const std::function<fnconv_t> &func) {
			return connect(func);
		}

		Event<fnconv_t>& operator-=(const SEventDelegate<fnconv_t> &deleg) {
			remove(deleg.token);
			return *this;
		}

		SEventDelegate<fnconv_t> connect(const std::function<fnconv_t> &func) {
			std::lock_guard<std::mutex> g(m_mutex);
			SEventDelegate<fnconv_t> deleg = { m_tokenMgr.get(), func };
			m_delegates[deleg.token] = deleg;
			return deleg;
		}

		void remove(const uint64 &token) {
			std::lock_guard<std::mutex> g(m_mutex);
			auto itr = m_delegates.find(token);
			if (itr != m_delegates.end()) {
				m_tokenMgr.remove(token);
				m_delegates.erase(itr);
			}
		}

		void clear() {
			m_mutex.lock();
			m_delegates.clear();
			m_tokenMgr.reset();
			m_mutex.unlock();
		}

	protected:
		TokenManager m_tokenMgr;
		mutable std::mutex m_mutex;
		std::map<uint64, SEventDelegate<fnconv_t>> m_delegates;

		template <typename T, typename CompT>
		typename std::enable_if<std::is_same<void, T>::value, T>::type
			_invoke_impl(const A0 &a0, const A1 &a1, const A2 &a2) const {
			m_mutex.lock();
			auto mDelegates = m_delegates;
			m_mutex.unlock();

			for (auto& pair: mDelegates)
				pair.second.callback(a0, a1, a2);
		}

		template <typename T, typename CompT>
		typename std::enable_if<!std::is_same<void, T>::value, T>::type
			_invoke_impl(const A0 &a0, const A1 &a1, const A2 &a2) const {
			m_mutex.lock();
			auto mDelegates = m_delegates;
			m_mutex.unlock();

			std::vector<T> results;
			results.reserve(mDelegates.size());
			for (auto& pair: mDelegates)
				results.push_back(pair.second.callback(a0, a1, a2));
			return CompT::Comp<T>(results.cbegin(), results.cend());
		}
	};

	//////////// EVENT P4 //////////////

	// Threadsafe
	template <typename R, typename A0, typename A1, typename A2, typename A3>
	class Event<R (A0, A1, A2, A3)>
	{
	public:
		typedef R fnconv_t(A0, A1, A2, A3);
		Event() : m_mutex(), m_tokenMgr(), m_delegates() {}

		Event(const std::function<fnconv_t> &func) :
			m_mutex(), m_tokenMgr(), m_delegates() {
			connect(func);
		}

		Event(Event<fnconv_t> &&other) :
			m_tokenMgr(std::move(other.m_tokenMgr)),
			m_mutex(std::move(other.m_mutex)),
			m_delegates(std::move(other.m_delegates)) {}

		Event<fnconv_t>& operator=(Event<fnconv_t> &&other) {
			m_tokenMgr = std::move(other.m_tokenMgr);
			m_mutex = std::move(other.m_mutex);
			m_delegates = std::move(other.m_delegates);
			return *this;
		}

		R operator()(const A0 &a0, const A1 &a1, const A2 &a2, const A3 &a3) const {
			return _invoke_impl<R, Maximum>(a0, a1, a2, a3);
		}

		template <typename CompT>
		R invoke(const A0 &a0, const A1 &a1, const A2 &a2, const A3 &a3) const {
			return _invoke_impl<R, CompT>(a0, a1, a2, a3);
		}

		SEventDelegate<fnconv_t> operator+=(const std::function<fnconv_t> &func) {
			return connect(func);
		}

		Event<fnconv_t>& operator-=(const SEventDelegate<fnconv_t> &deleg) {
			remove(deleg.token);
			return *this;
		}

		SEventDelegate<fnconv_t> connect(const std::function<fnconv_t> &func) {
			std::lock_guard<std::mutex> g(m_mutex);
			SEventDelegate<fnconv_t> deleg = { m_tokenMgr.get(), func };
			m_delegates[deleg.token] = deleg;
			return deleg;
		}

		void remove(const uint64 &token) {
			std::lock_guard<std::mutex> g(m_mutex);
			auto itr = m_delegates.find(token);
			if (itr != m_delegates.end()) {
				m_tokenMgr.remove(token);
				m_delegates.erase(itr);
			}
		}

		void clear() {
			m_mutex.lock();
			m_delegates.clear();
			m_tokenMgr.reset();
			m_mutex.unlock();
		}

	protected:
		TokenManager m_tokenMgr;
		mutable std::mutex m_mutex;
		std::map<uint64, SEventDelegate<fnconv_t>> m_delegates;

		template <typename T, typename CompT>
		typename std::enable_if<std::is_same<void, T>::value, T>::type
			_invoke_impl(const A0 &a0, const A1 &a1, const A2 &a2, const A3 &a3) const {
			m_mutex.lock();
			auto mDelegates = m_delegates;
			m_mutex.unlock();

			for (auto& pair: mDelegates)
				pair.second.callback(a0, a1, a2, a3);
		}

		template <typename T, typename CompT>
		typename std::enable_if<!std::is_same<void, T>::value, T>::type
			_invoke_impl(const A0 &a0, const A1 &a1, const A2 &a2, const A3 &a3) const {
			m_mutex.lock();
			auto mDelegates = m_delegates;
			m_mutex.unlock();

			std::vector<T> results;
			results.reserve(mDelegates.size());
			for (auto& pair: mDelegates)
				results.push_back(pair.second.callback(a0, a1, a2, a3));
			return CompT::Comp<T>(results.cbegin(), results.cend());
		}
	};
#endif
}
Anwendungsbeispiel:

Code:
// Event erstellen...
Utils::Event<uint32 (uint32, uint32)> someEvent;

// Multiply Event registrieren...
auto mulDelegate = someEvent +=
[] (uint32 a, uint32 b) {
    return a * b;
};

// Add-Event registrieren...
auto addDelegate = someEvent +=
[] (uint32 a, uint32 b) {
    return a + b;
};

// Event mit Standard-Comparer (Utils::Maximum) aufrufen...
std::cout << someEvent(3, 5) << std::endl;

// Event mit explizitem Comparer (Utils::Minimum) aufrufen...
std::cout << someEvent.invoke<Utils::Minimum>(3, 5) << std::endl;

// Multiply-Event löschen...
someEvent -= mulDelegate;

// Nochmals mit Standard-Comparer (Utils::Maximum) aufrufen...
std::cout << someEvent(3, 5) << std::endl;

// Ausgabe: 15, 8, 8
Benutze zum registrieren der Events noch meinen TokenManager, der sieht einfach so aus:

Code:
namespace Utils
{
	class TokenManager
	{
	public:
		TokenManager() : m_curMaxToken(0) {}

		TokenManager(TokenManager &&other) :
			m_curMaxToken(other.m_curMaxToken),
			m_freeTokens(std::move(other.m_freeTokens)) {
			other.m_curMaxToken = 0;
		}

		TokenManager& operator = (TokenManager &&other) {
			m_curMaxToken = other.m_curMaxToken;
			m_freeTokens = std::move(other.m_freeTokens);

			other.m_curMaxToken = 0;
			return *this;
		}

		uint64 get() {
			if (m_freeTokens.empty())
				return ++m_curMaxToken;

			auto itr = m_freeTokens.begin();
			uint64 curToken = *itr;
			m_freeTokens.erase(itr);
			return curToken;
		}

		void remove(const uint64 &token) {
			if (token != 0) {
				if (token == m_curMaxToken)
					--m_curMaxToken;

				else if (token < m_curMaxToken)
					m_freeTokens.insert(token);
			}
		}

		bool is_free(const uint64 &token) const {
			return token > m_curMaxToken ||
				m_freeTokens.find(token) != m_freeTokens.end();
		}

		void reset() {
			m_curMaxToken = 0;
			m_freeTokens.clear();
		}

	protected:
		uint64 m_curMaxToken;
		std::set<uint64> m_freeTokens;
	};
}
04/11/2013 16:54 Omdi#2
Sieht sehr hübsch aus, dankeschön kommt mir gerade gelegen :D
04/20/2013 21:25 Master674b#3
Comparer Mechanik hinzugefügt. Anwendungsbeispiel oben.
04/20/2013 21:29 Schlüsselbein#4
Darf ich fragen, warum du die Lösung aus den boost Bibliotheken unbequem findest?
04/20/2013 22:18 Master674b#5
Quote:
Originally Posted by Schlüsselbein View Post
Darf ich fragen, warum du die Lösung aus den boost Bibliotheken unbequem findest?
Ich vermisse Lambda Support und rv-comparer beim Aufruf.
Außerdem wollte ich einfach mal was mit variadischen Templates und SFINAE gemacht haben.

boost::signal war früher außerdem nicht Thread sicher. Keine Ahnung ob das mittlerweile anders ist,
außerdem ist es non copyable.

Edit: Oh da fällt mir ein, das letzte mal als ich boost::signals verwendet hatte gab es dauernd einen Crash. Eben nochmal getestet:

Code:
boost::signal<void (uint32, uint32)> someSignal;
someSignal(1, 2);
Keine Ahnung was da los ist, müsste ich mal Debuggen aber interessiert mich eigentlich auch gar nicht.