首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >简单事件机制

简单事件机制
EN

Code Review用户
提问于 2018-11-01 13:26:44
回答 2查看 253关注 0票数 3

我正在为c++开发一个简单的事件机制。由于我不是很有经验,我想分享我的代码,以获得一些想法。

我也不喜欢BIND_X宏。如何简化绑定过程的想法是欢迎的,以及任何关于问题,不良风格的意见。

代码语言:javascript
复制
#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个参数的静态函数):

代码语言:javascript
复制
#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 (定义在接收方内部)满足这一要求,人们可以这样使用它:

代码语言:javascript
复制
auto handle = s->e->Attach(BIND_3(receiver::callMe, r));

编辑:下面是一个如何避免BIND_X宏的例子(这就是为什么我要删除它们)。

代码语言:javascript
复制
auto handle = s->e->Attach([r](const std::string& s, int a, int b) {r->callMe(s, a, b);});
EN

回答 2

Code Review用户

回答已采纳

发布于 2018-11-02 10:21:52

使用std::function,您不能确保签名匹配(如果参数是可转换的,它就能工作)。有些function_traits可以在这里应用。

尝试在编译时检查给定回调的签名是否与您预期的匹配。

Invoke中的循环可以通过使用基于范围的for循环或std::for_each来简化.

您不必在任何地方都编写this->

您考虑过使用队列而不是列表吗?在语义上,它更正确

UniqueId生成可以从这个类中暴露出来,因为它是一个不同的关注点,可能是可重用的。

对于“绑定”问题,作为回调,用户可以传递捕获实例的lambda。

注意:我修改了您的“示例”,以便在一个工作示例中获得它,但没有修复代码原件中的内存泄漏。newdelete是一对老夫妇,他们从不单独外出。

编辑:我不可能继承事件类。如果是我的类,我宁愿选择一个tagged_type,这样每个事件都具有不同的类型,即使签名匹配,即使签名匹配。但是这个设计的选择是一个品味的问题。

如果您想更好地理解function_traits,可以检查

票数 1
EN

Code Review用户

发布于 2018-11-04 15:55:34

花了一段时间,但功能特性起作用了。要使检查实际工作,我必须从附加函数中移除继承参数类型,并使其成为模板函数本身。(类型转换将在函数内部的static_assert发生之前进行)

也许我会删除function_traits中尚未使用的部分(重要性,返回类型)。还不确定。

event.hpp:

代码语言:javascript
复制
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 namespace

function_traits.hpp:

代码语言:javascript
复制
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

我还将随机数生成器移动到一个单独的头文件中。

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/206723

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档