我正在为c++开发一个简单的事件机制。由于我不是很有经验,我想分享我的代码,以获得一些想法。
我也不喜欢BIND_X宏。如何简化绑定过程的想法是欢迎的,以及任何关于问题,不良风格的意见。
#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示例用法(假设CallMe是一个带有2个参数的静态函数):
#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;
}注意: bind宏用于简化方法的绑定,这些方法需要某种类型的实例调用。假设类S公开一个事件e,该事件e需要3个参数,而callMe (定义在接收方内部)满足这一要求,人们可以这样使用它:
auto handle = s->e->Attach(BIND_3(receiver::callMe, r));编辑:下面是一个如何避免BIND_X宏的例子(这就是为什么我要删除它们)。
auto handle = s->e->Attach([r](const std::string& s, int a, int b) {r->callMe(s, a, b);});发布于 2018-11-02 10:21:52
使用std::function,您不能确保签名匹配(如果参数是可转换的,它就能工作)。有些function_traits可以在这里应用。
尝试在编译时检查给定回调的签名是否与您预期的匹配。
Invoke中的循环可以通过使用基于范围的for循环或std::for_each来简化.
您不必在任何地方都编写this->。
您考虑过使用队列而不是列表吗?在语义上,它更正确
UniqueId生成可以从这个类中暴露出来,因为它是一个不同的关注点,可能是可重用的。
对于“绑定”问题,作为回调,用户可以传递捕获实例的lambda。
注意:我修改了您的“示例”,以便在一个工作示例中获得它,但没有修复代码原件中的内存泄漏。new和delete是一对老夫妇,他们从不单独外出。
编辑:我不可能继承事件类。如果是我的类,我宁愿选择一个tagged_type,这样每个事件都具有不同的类型,即使签名匹配,即使签名匹配。但是这个设计的选择是一个品味的问题。
如果您想更好地理解function_traits,可以检查这。
发布于 2018-11-04 15:55:34
花了一段时间,但功能特性起作用了。要使检查实际工作,我必须从附加函数中移除继承参数类型,并使其成为模板函数本身。(类型转换将在函数内部的static_assert发生之前进行)
也许我会删除function_traits中尚未使用的部分(重要性,返回类型)。还不确定。
event.hpp:
namespace event {
// The type of the source identifier passed to each event listener
typedef const std::string& source_t;
template<typename... args>
using func = std::function<void(source_t, args...)>;
///
/// \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:
///
/// \brief Create new instance
/// \param source The name of the event source.
///
Event(source_t source) :
source(source)
{}
///
/// \brief Release resources
///
~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
///
template <class... args>
listener_handle& Attach(const func<args...>& newListener)
{
using listener_traits = ftraits::function_traits<typename std::decay<decltype(newListener)>::type>;
ftraits::assert_traits_equal<listener_traits, traits>();
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
///
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
///
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:
using traits = ftraits::function_traits<func<T...>>;
struct Listener {
func<T...> listener;
listener_handle handle;
};
///
/// \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, createRandom()};
}
std::string source;
std::forward_list<Listener> listeners;
};
} //event namespacefunction_traits.hpp:
namespace ftraits {
///
/// \brief Undefined base
///
template<typename>
struct function_traits;
///
/// \brief Specialization for std::function
///
template <typename Function>
struct function_traits : public function_traits<decltype(&Function::operator())>
{ };
///
/// \brief Function traits implementation
///
template <typename ClassType, typename ReturnType, typename... Arguments>
struct function_traits<ReturnType(ClassType::*)(Arguments...) const>
{
typedef ReturnType result_type;
static constexpr std::size_t arity = sizeof...(Arguments);
template <std::size_t N>
struct argument
{
using type = typename std::tuple_element<N, std::tuple<Arguments...>>::type;
};
};
template<typename t1, typename t2>
///
/// \brief Check the the function traits are equal using static assert
///
void assert_traits_equal()
{
static_assert(std::is_same<t1, t2>::value, "The function signatures do not match.");
};
}
#endif // FUNCTION_TRAITS_H我还将随机数生成器移动到一个单独的头文件中。
https://codereview.stackexchange.com/questions/206723
复制相似问题