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