eventpp 是一个 header-only 、简练易用的 C++ 事情库,能为 C++ 程序增加类似于 Qt 信号槽体系的事情机制。本专栏以该库文档的中文翻译为主,内容已奉献到该库的代码仓

源码仓库:github.com/wqking/even…

CallbackList 运用教程

留意:如果想尝试运转教程代码,主张运用 tests/unittest 目录下的代码。本文中的示例代码或许现已过期而无法编译。

CallbackList 教程 1, 基础

代码

// 命名空间是 eventpp
// 首个参数是监听器的原型
eventpp::CallbackList<void ()> callbackList;
​
// 增加一个回调函数,此处即 [](){} 。回调函数并非必定要是 lambda 表达式。
// 函数、std::function 或其他任何满意监听器原型要求的函数方针都可以作为监听器
callbackList.append([](){
  std::cout << "Got callback 1." << std::endl;
});
callbackList.append([](){
  std::cout << "Got callback 2." << std::endl;
});
​
// 发动回调列表
callbackList();

输出

Got callback 1.
Got callback 2.

解读

首要,界说一个回调列表( callback list )

eventpp::CallbackList<void ()> callbackList;

CallbackList 需求至少一个模板参数,作为回调函数的“原型”( prototype )。 “原型”指 C++ 函数类型,例如 void (int), void (const std::string &, const MyClass &, int, bool)

然后,增加一个回调函数

callbackList.append([]() {
  std::cout << "Got callback 1." << std::endl;
});

append 函数接纳一个回调函数作为参数。 回调函数可以使任何回调方针——函数、函数指针、指向成员函数的指针、lambda 表达式、函数方针等。该回调函数有必要可以运用 callbackList 中声明的原型调用。

接下来发动回调列表

callbackList();

在回调列表发动履行的过程中,一切回调函数都会依照被参加列表时的次序履行。

CallbackList 教程 2, 带参数的回调函数

代码

// 下面这个 CallbackList 的回调函数原型有两个参数
eventpp::CallbackList<void (const std::string &, const bool)> callbackList;
​
callbackList.append([](const std::string & s, const bool b) {
  std::cout<<std::boolalpha<<"Got callback 1, s is " << s << " b is " << b << std::endl;
});
​
// 回调函数原型不需求和回调函数列表完全一致。只需参数类型兼容即可
callbackList.append([](std::string s, int b) {
  std::cout<<std::boolalpha<<"Got callback 2, s is " << s << " b is " << b << std::endl;
});
​
// 发动回调列表
callbackList("Hello world", true);

输出

Got callback 1, s is Hello world b is true
Got callback 2, s is Hello world b is 1

解读

本例中,回调函数列表的回调函数原型接纳两个参数: const std::string &const bool。 回调函数的原型并不需求和回调完全一致,只需两个函数中的参数可以兼容即可。正如上面例子中的第二个回调函数,其参数为 [](std::string s, int b),其原型与回调列表中的并不相同。

CallbackList 教程 3, 移除

代码

using CL = eventpp::CallbackList<void ()>;
CL callbackList;
​
CL::Handle handle2;
​
// 加一些回调函数
callbackList.append([]() {
  std::cout << "Got callback 1." << std::endl;
});
handle2 = callbackList.append([]() {
  std::cout << "Got callback 2." << std::endl;
});
callbackList.append([]() {
  std::cout << "Got callback 3." << std::endl;
});
​
callbackList.remove(handler2);
​
// 发动回调列表。“Got callback 2.” 并不会被触发
callbackList();

输出

Got callback 1.
Got callback 3.

CallbackList 教程 4, for each

代码

using CL = eventpp::CallbackList<void ()>;
CL callbackList;
​
// 增加回调函数
callbackList.append([]() {
  std::cout << "Got callback 1." << std::endl;
});
callbackList.append([]() {
  std::cout << "Got callback 2." << std::endl;
});
callbackList.append([]() {
  std::cout << "Got callback 3." << std::endl;
});
​
// 下面调用 forEach 移除第二个回调函数
// forEach 回调函数的原型是
// void(const CallbackList::Handle & handle, const CallbackList::Callback & callback)
int index = 0;
callbackList.forEach([&callbackList, &index](const CL::Handle & handle, const CL::Callback & callback) {
  std::cout << "forEach(Handle, Callback), invoked " << index << std::endl;
  if(index == 1) {
    callbackList.remove(handle);
    std::cout << "forEach(Handle, Callback), removed second callback" << std::endl;
   }
  ++index;
});
​
// forEach 回调函数原型也可所以 void(const CallbackList::Callback & callback)
callbackList.forEach([&callbackList, &index](const CL::Callback & callback) {
  std::cout << "forEach(Callback), invoked" << std::endl;
});
​
// 发动回调列表。“Got callback 2.” 并不会被触发
callbackList();

输出

forEach(Handle, Callback), invoked 0
forEach(Handle, Callback), invoked 1
forEach(Handle, Callback), removed second callback
forEach(Handle, Callback), invoked 2
forEach(Callback), invoked
forEach(Callback), invoked
Got callback 1.
Got callback 3.

EventDispatcher 运用教程

留意:如果想尝试运转教程代码,主张运用 tests/unittest 目录下的代码。本文中的示例代码或许现已过期而无法编译。

教程 1 基本用法

代码

// 命名空间为 eventpp
// 第一个模板参数 int 是事情类型。事情类型可所以其他数据类型的,如 std::string,int 等
// 第二个参数是监听器的原型
eventpp::EventDispatcher<int, void ()> dispatcher;
​
// 增加一个监听器。这里的 3 和 5 是传给 dispatcher 的,用于标记自身的事情类型
// []() {} 是监听器。
// 监听器并不有必要是 lambda,可以使任何满意原型要求的可调用方针,如函数、std::function等
dispatcher.appendListener(3, []() {
  std::cout << "Got event 3." << std::endl;
});
dispatcher.appendListener(5, []() {
  std::cout << "Got event 5." << std::endl;
});
dispatcher.appendListener(5, []() {
  std::cout << "Got another event 5." << std::endl;
});
​
// 分发事情。第一个参数是事情类型。
dispatcher.dispatch(3);
dispatcher.dispatch(5);

输出

Got event 3.
Got event 5.
Got another event 5.

解读

首要界说一个分发器

eventpp::EventDispatcher<int, void ()> dispatcher;

EventDispatcher 类接纳两个模板参数。第一个是事情类型,此处是 int 。第二个是监听器的原型事情类型 有必要可以用作 std::map 的 key。也便是说该类型有必要支撑 operator <原型 是 C++ 函数类型,例如 void (int), void (const std::string &, const MyClass &, int, bool)

然后增加一个监听器

dispatcher.appendListener(3, []() {
    std::cout << "Got event 3." << std::endl;
});

appendListener 函数接纳两个参数。第一个是 事情类型事情 (译注:此处的“事情类型”指的是用于区别事情的数据类型,此处为 int 。“事情”则是详细的时间值,此处为整数 3 ),此处为 int 类型。第二个参数是回调函数。 回调函数可所以任何可以回调的方针——函数、函数指针、成员函数指针、lambda表达式、函数方针等。其有必要可以被 dispatcher 中声明的 原型 调用。 在上面这段代码的下面,咱们还为 事情5 增加了两个监听器。

接下来,运用下面的代码分发事情

dispatcher.dispatch(3);
dispatcher.dispatch(5);

这里分发了两个事情,分别是事情 3 和 5 。 在事情分发的过程中,一切对应事情的监听器都会依照它们被增加进 EventDispatcher 的次序逐个履行。

教程 2 —— 带参数的监听器

代码

// 界说有两个参数的监听器原型
eventpp::EventDispatcher<int, void (const std::string &, const bool)> dispatcher;
​
dispatcher.appendListener(3, [](const std::string & s, const bool b) {
  std::cout << std::boolalpha << "Got event 3, s is " << s << " b is " << b << std::endl;
});
// 监听器的原型不需求和 dispatcher 完全一致,只需参数类型可以兼容即可
dispatcher.appendListener(5, [](std::string s, int b) {
  std::cout << std::boolalpha << "Got event 5, s is " << s << " b is " << b << std::endl;
});
dispatcher.appendListener(5, [](const std::string & s, const bool b) {
  std::cout << std::boolalpha << "Got another event 5, s is " << s << " b is " << b << std::endl;
});
​
// 分发事情。第一个参数是事情类型
dispatcher.dispatch(3, "Hello", true);
dispatcher.dispatch(5, "World", false);

输出

Got event 3, s is Hello b is true
Got event 5, s is World b is 0
Got another event 5, s is World b is false

解读

此处的 dispatcher 回调函数原型接纳两个参数:const std::string &const bool。 监听器原型不需求和 dispatcher 完全一致,只需参数类型可以兼容即可。例如第二个监听器,[](std::string s, int b),其原型和 dispatcher 并不相同

教程 3 —— 自界说事情结构

代码

// 界说一个可以保存一切参数的 Event
struct MyEvent {
  int type;
  std::string message;
  int param;
};
​
// 界说一个能让 dispatcher 知道怎么展开事情类型的 policy
struct MyEventPolicies
{
  static int getEvent(const MyEvent & e, bool /*b*/) {
    return e.type
   }
};
​
// 将刚刚界说的 MyEventPolicies 用作 EventDispatcher 的第三个模板参数
// 留意:第一个模板参数是事情类型的类型 int ,并非 MyEvent
eventpp::EventDispatcher<
  int,
    void (const MyEvent &, bool),
    MyEventPolicies
> dispatcher;
​
// 增加一个监听器。留意,第一个参数是事情类型 int,并非 MyEvent
dispatcher.appendListener(3, [](const MyEvent & e, bool b) {
  std::cout
    << std::boolalpha
    << "Got event 3" << std::endl
    << "Event::type is " << e.type << std::endl
    << "Event::message is " << e.message << std::endl
    << "Event::param is " << e.param << std::endl
    << "b is " << b << std::endl
   ;
});
​
// 发动事情。第一个参数是 Event
dispatcher.dispatch(MyEvent { 3, "Hello world", 38 }, true);

输出

Got event 3
Event::type is 3
Event::message is Hello world
Event::param is 38
b is true

解读

一般的方法是将 Event 类界说为基类,一切其他的事情都从 Event 派生,实际的事情类型则是 Event 的成员(就像 Qt 中的 QEvent ),通过 policy 来为 EventDispatcher 界说怎么从 Event 类中获取真正需求的数据。

EventQueue 运用教程

留意:如果想尝试运转教程代码,主张运用 tests/unittest 目录下的代码。本文中的示例代码或许现已过期而无法编译。

教程 1 基本用法

代码

eventpp::EventQueue<int, void (const std::string &, std::unique_ptr<int> &)> queue;
​
queue.appendListener(3, [](const std::string & s, std::unique_ptr<int> & n) {
  std::cout << "Got event 3, s is " << s << " n is " << *n << std::endl;
});
​
// 监听器原型不需求和 dispatcher 完全一致,参数类型兼容即可
queue.appendListener(5, [](std::string s, const std::unique_ptr<int> & n) {
  std::cout << "Got event 5, s is " << s << " n is " << *n << std::endl;
});
queue.appendListener(5, [](const std::string & s, std::unique_ptr<int> & n) {
  std::cout << "Got another event 5, s is " << s << " n is " << *n << std::endl;
});
​
// 将事情参加行列,首个参数是事情类型。监听器在入行列期间不会被触发
queue.enqueue(3, "Hello", std::unique_ptr<int>(new int(38)));
queue.enqueue(5, "World", std::unique_ptr<int>(new int(58)));
​
// 处理事情行列,分发行列中的一切事情
queue.process();

输出

Got event 3, s is Hello n is 38
Got event 5, s is World n is 58
Got another event 5, s is World n is 58

解读 EventDispatcher<>::dispatch() 触发监听器的动作是同步的。但异步事情行列在某些场景下能发挥更大的作用(例如 Windows 消息行列、游戏中的消息行列等)。EventQueue 便是用于满意该类需求的事情行列。 EventQueue<>::enqueue() 将事情参加行列,其参数和 dispatch 的参数完全相同。 EventQueue<>::process() 用于分发行列中的事情。不调用 process ,事情就不会被分发。 事情行列的典型用例:在 GUI 使用中,每个组件都调用 EventQueue<>::enqueue() 来发布事情,然后主事情循环调用 EventQueue<>()::process() 来 dispatch 一切行列中的事情。 EventQueue 支撑将不可复制方针作为事情参数,例如上面例子中的 unique_ptr

教程 2 —— 多线程

代码

using EQ = eventpp::EventQueue<int, void (int)>;
EQ queue;
​
constexpr int stopEvent = 1;
constexpr int otherEvent = 2;
​
// 发动一个新线程来处理事情行列。一切监听器都会在该线程中发动运转
std::thread thread([stopEvent, otherEvent, &queue]() {
    volatile bool shouldStop = false;
  queue.appendListener(stopEvent, [&shouldStop](int) {
    shouldStop = true;
   });
  queue.appendListener(otherEvent, [](const int index) {
    std::cout << "Got event, index is " << index << std::endl;
   });
  
  while(! shouldStop) {
    queue.wait();
    
    queue.process();
   }
});
​
// 将一个主线程的事情参加行列。在休眠 10 ms 时,该事情应该现已被另一个线程处理了
queue.enqueue(otherEvent, 1);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::cout << "Should have triggered event with index = 1" << std::endl;
​
queue.enqueue(otherEvent, 2);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::cout << "Should have triggered event with index = 2" << std::endl;
​
{
  // EventQueue::DisableQueueNotify 是一个 RAII 类,能防止唤醒其他的等候线程。
  // 所以该代码块内不会触发任何事情。
  // 当需求一次性增加很多事情,期望在事情都增加完成后才唤醒等候线程时,
  // 就可以运用 DisableQueueNotify 
  EQ::DisableQueueNotify disableNotify(&queue);
  
  queue.enqueue(otherEvent, 10);
  std::this_thread::sleep_for(std::chrono::milliseconds(10));
  std::cout << "Should NOT trigger event with index = 10" << std::endl;
  
  queue.enqueue(otherEvent, 11);
  std::this_thread::sleep_for(std::chrono::milliseconds(10));
  std::cout << "Should NOT trigger event with index = 11" << std::endl;
}
// DisableQueueNotify 方针在此处销毁,恢复唤醒其他的等候线程。因而事情都会在此处触发
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::cout << "Should have triggered events with index = 10 and 11" << std::endl;
​
queue.enqueue(stopEvent, 1);
thread.join();

输出

Got event, index is 1
Should have triggered event with index = 1
Got event, index is 2
Should have triggered event with index = 2
Should NOT trigger event with index = 10
Should NOT trigger event with index = 11
Got event, index is 10
Got event, index is 11
Should have triggered events with index = 10 and 11