eventpp 是一个 header-only 、简洁易用的 C++ 事情库,能为 C++ 程序增加类似于 Qt 信号槽体系的事情机制。本专栏以该库文档的中文翻译为主,内容已奉献到该库的代码仓
源码库房:wqking/eventpp: Event Dispatcher and callback list for C++ (github.com)
CallbackList 类参考手册
目录
描绘
CallbackList 是 eventpp 中最为核心、根底的类。EventDispatcher、EventQueue 都是以 CallbackList 类为根底开发的。
CallbackList 内部保护一个回调函数的列表。在 CallbackList 被调用时,其会逐一调用列表中的一切回调函数。可以将 CallbackList 看做 Qt 中的信号/槽体系,或某些 Windows API 中的回调函数指针(例如 ReadFileEx
中的 IpCompletionRoutine )。 回调函数可以是任何回调方针 —— 函数、函数指针、指向成员函数的指针、lambda 表达式、函数目标等。
API 参考
头文件
eventpp/callbacklist.h
模板参数
template <
typename Prototype,
typename Policies = DefaultPolicies
>
class CallbackList;
Prototype
:回调函数原型。该参数应为 C++ 函数类型,如 void(int, std::string, const MyClass *)
。 Policies
:用于配置和扩展回调函数列表的规矩。默认值为 DefaultPolicies
。详见 policy 文档
公共类型
Handle
:由 append
、prepend
、insert
函数回来的句柄类型。句柄可用于刺进或移除一个回调函数。可以通过将句柄转为 bool 类型来查看其是否为空:句柄为空时值为 false
。Handle
时可仿制的。
成员函数
结构函数
CallbackList() noexcept;
CallbackList(const CallbackList & other);
CallbackList(CallbackList && other) noexcept;
CallbackList & operator = (const CallbackList & other);
CallbackList & operator = (CallbackList && other) noexcept;
void swap(CallbackList & other) noexcept;
CallbackList 可以被仿制、move、赋值、move 赋值和交流。
empty
bool empty() const;
当回调列表为空时回来 true 。 提示:在多线程程序中,该函数的回来值无法确保确认便是列表的实在状况。或许会呈现刚刚回来 true 之后列表立刻变为非空的情况,反之亦然。
bool 转换运算符
operator bool() const;
若回调列表非空则回来 true。 凭借该运算符,可以完成在条件句子中运用 CallbackList 实例
append
Handle append(const Callback & callback);
向回调函数列表中增加回调函数。 新的回调函数会被加在回调函数列表的结尾。 该函数回来一个代表回调函数的句柄。该句柄可以用于移除该回调函数,或在该回调函数前刺进其他的回调函数。 假如append
在回调函数列表履行的进程中被其他的回调函数调用,则新增加的回调函数必定不会在该回调函数列表履行的进程中被履行。 该函数的时刻复杂度为 O(1) 。
prepend
Handle prepend(const Callback & callback);
向回调函数列表中增加回调函数。 回调函数将被加在回调函数列表的最前端。 该函数会回来一个代表回调函数的句柄(handler)。该句柄可被用于移除该回调函数,也可用于在该回调函数前刺进其他回调函数。 假如 prepend
在回调函数列表履行的进程中被其他回调函数调用,则新增加的回调函数必定不会在该回调函数列表履行的进程中被履行。 该函数的时刻复杂度为 O(1) 。
insert
Handle insert(const Callback & callback, const Handle & before);
将 callback 刺进到回调列表中 before 前面的一个方位处。若找不到 before ,则 callback 会被加到回调列表的结尾。 该函数回来一个代表回调函数的句柄。该句柄可被用于移除该回调函数,也可用于在该回调函数前刺进其他回调函数。 假如 insert
是在回调函数列表履行的进程中被其他回调函数调用的,则新增加的回调函数必定不会在该回调函数列表履行的进程中被履行。 该函数的时刻复杂度为 O(1) 。
提示:该函数的调用者有必要提早确认 before
是由 this
所指向的 CallbackList 调用的。假如不能确认,可以用 ownsHandle
函数来查看 before
是否属于 this
CallbackList 。insert
函数仅能在 ownsHandle(before)
回来 true 的时分被调用,不然或许引发未界说的行为并带来怪异的 bug 。 需求确保只在 assert(ownsHandle(before))
时 insert
,但出于功能方面的考量,发布的代码中没有包含相关的查看。
remove
bool remove(const Handle & handle);
从回调函数列表中移除 handle 指向的回调函数。 移除成功会回来 true ,找不到回调函数则会回来 false 。 该函数的时刻复杂度为 O(1)
提示:handle
有必要是由 this
指向的 CallbackList 所创立的。更多细节请查看 insert
中的“提示”部分
ownsHandle
bool ownsHandle(const Handle & handle) const;
当 handle
是由当时 CallbackList 创立时回来 true,不然回来 false 。 该函数时刻复杂度为 O(N)
forEach
template <typename Func>
void forEach(Func && func) const;
对一切的回调函数运用 func
。 func
可以是下面两种原型的其间一种:
AnyReturnType func(const CallbackList::Handle &, const CallbackList::Callback &);
AnyReturnType func(const CallbackList:Callback &);
提示:func
可以安全地移除和增加新的回调函数
forEachIf
template <typename Func>
bool forEachIf(Func && func) const;
对一切的回调函数运用 func
。 func
有必要回来一个 bool 值,若回来值为 false ,则 forEachIf 将会当即中止循环。 当一切回调函数都被触发后,或未找到 event
时,该函数会回来 true 。当 func
回来 false
时回来 false
调用运算符
void operator() (Args ...args) const;
触发回调函数列表中一切回调函数的运转。 回调函数会被用 args
参数作为参数调用。 回调函数会在 operator()
所在的线程中调用。
嵌套回调函数安全性
- 若一个回调函数在本身运转时,向回调函数列表中增加了新的回调函数,则新的回调函数不会在本次回调函数列表履行的进程中被履行。该行为是由一个 64 位无符号类型的计数器所确保的。假如在一次触发的进程中使该计数器溢出到 0 则会破坏上述的行为。
- 一切在回调函数列表运转进程中被移除的回调函数都不会被触发运转。
- 上面这几点在多线程情况下未必建立。也便是说,假如一个线程正在履行回调函数列表,假如另一个线程的函数向这个列表中增加或移除了函数,则被增加或移除的函数仍有或许在此次回调函数列表履行的进程中被履行。
时刻复杂度
-
append
:O(1) -
prepend
:O(1) -
insert
:O(1) -
remove
:O(1)
内部数据结构
CallbackList 运用双向链表办理回调函数。 每个节点都运用同享指针(shared pointer)连接。运用同享指针可以完成在迭代的进程中移除节点。
EventDispatcher (事情调度器)类参考手册
目录
-
描绘
-
API 参考
- 头文件
- 模板参数
- 公共类型
- 成员函数
-
嵌套监听器安全
-
时刻复杂度
-
内部数据结构
描绘
EventDispatcher 类似于 std::map<EventType, CallbackList>
。
EventDispatcher 保护一个 <EventType, CallbackList>
映射表。在进行事情调度时, EventDispatcher 会根据事情类型(EventType)查找并调用对应的回调列表(CallbackList) 。该调用进程是同步的,监听器会在 EventDispatcher::dispatch
被调用时触发。
API 参考
头文件
eventpp/eventdispatcher.h
模板参数
template <
typename Event,
typename Prototype,
typename Policies = DefaultPolicies
>
class EventDispatcher;
Event
:事情的类型。该类型用于识别事情,具有相同类型的事情便是同一个事情。事情类型有必要是能在 std::map
或 std::unordered_map
中用作 key 的类型,所以该类型应该要么可以运用 operator <
进行比较,要么派生自 std::hash
。
Prototype
:监听器的原型。其应为 C++ 函数类型,例如 void(int, std::string, const MyClass *)
。
Policies
:配置和扩展调度器的规矩。默认值是 DefaultPolicies
。 详情请阅览github.com/wqking/even… 文档。
公共类型
Handle
:由 appendListener
、prependListener
、insertListener
函数回来的 Handle
(句柄)类型。可用于刺进或移除一个监听器。可以通过将句柄转为 bool 类型来查看其是否为空:句柄为空时值为 false
。Handle
时可仿制的。
Callback
:回调函数存储类型。
Event
:事情类型。
成员函数
结构函数
EventDispatcher();
EventDispatcher(const EventDispatcher & other);
EventDispatcher(EventDispatcher && other) noexcept;
EventDispatcher & operator = (const EventDispatcher & other);
EventDispatcher & operator = (EventDispatcher && other) noexcept;
void swap(EventDispatcher & other) noexcept;
EventDispatcher 可以仿制、move、赋值、move 赋值和交流
appendListener
Handler appendListener(const Event & event, const Callback & callback);
向调度器中增加用于监听 event 事情的回调函数 callback。
监听器会被增加到监听器列表的结尾。
该函数会回来一个代表监听器的句柄。该句柄可用于移除该监听器,或在该监听器之前刺进其他监听器。
若 appendListener
在一次事情调度的进程中被另一个监听器调用,则新的监听器不会在本次事情调度的进程中被触发。
假如同一个回调函数被增加了两次,则会调度器中呈现两个相同的监听器。
该函数的时刻复杂度为 O(1) + 在内部映射表中寻觅事情的时刻
prependListener
Handle prependListener(const Event & event, const Callback & callback);
向调度器中增加用于监听 event 事情的回调函数 callback 。
监听器会被增加到监听器列表的最前端。
该函数回来一个代表该监听器的句柄。该句柄可用于移除该监听器,也可以用于在该监听器前面再刺进其他监听器。
若 prependListener
在一次事情调度的进程中被另一个监听器调用,则新的监听器不会在本次事情调度的进程中被触发。
该函数的时刻复杂度为 O(1) + 在内部映射表中寻觅事情的时刻。
insertListener
Handle insertListener(const Event & event, const Callback & callback, const Handle before);
将回调函数 callback 刺进到调度器中 before 监听器前面的一个方位,以监听 event 事情。假如没找到 before ,callback 会被增加到监听器列表的结尾。
该函数会回来一个代表该监听器的句柄。该句柄可用于移除监听器,也可以用于在该监听器前面再刺进其他监听器。
若 insertListener
在一次事情调度的进程中被另一个监听器调用,则用该函数新刺进的监听器不会在本次事情调度的进程中被触发。
该函数的时刻复杂度为 O(1) + 在内部映射表中寻觅事情的时刻。
留意:调用者有必要确保 before
句柄是由 this
EventDispatcher 创立的。若不能确认,则可用 ownsHandle
来查看 before
句柄是否属于 this
EventDispatcher。 insert
函数只能在 ownsHandle(before)
回来 true 时才干被调用,不然会呈现未界说的行为,导致呈现怪异的 bug。
insertListener
会在本身的回调列表中进行一次 assert(ownsHandle(before))
,但出于功能方面的考量,在发布的代码中并不会进行查看。
removeListener
bool removeListener(const Event & event, const Handle handle);
移除调度器中 handle 所指向的监听 event 的监听器。
若监听器被成功移除,该函数回来 tue。若未找到对应监听器则回来 false。
该函数的时刻复杂度为 O(1) + 在内部映射表中寻觅事情的时刻。
留意:handle
有必要是由 this
EventDispatcher 创立的。详细阐明请查看 insertListener
中的留意部分。
hasAnyListener
bool hasAnyListener(const Event & event) const;
当存在与 event
对应的监听器时回来 ture ,不然回来 false 。
留意:在多线程中,该函数回来 true 时并不能确保 event 中必定存在监听器。回调列表或许在该函数回来 true 后就被清空了,反之亦然。该函数的时刻复杂度为 O(1) + 在内部映射表中寻觅事情的时刻。
ownsHandle
bool ownsHandle(const Event & event, const Handle & handle) const;
若 handle
是由 EventDispatcher 为 event
创立的,回来 true,不然回来 false。
时刻复杂度为 O(N)
forEach
template <typename Func>
void forEach(const Event & event, Func && func);
对 event
的一切监听器运用 func
。
func
可以是下面两个属性中的一个:
AnyReturnType func(const EventDispatcher::Handle &, const EventDispatcher::Callback &);
AnyReturnType func(const EventDispatcher::Callback &);
留意:func
可以安全地移除任何监听器或增加其他的监听器。
forEachIf
template <typename Func>
bool forEachIf(const Event & event, Func && func);
对 event
的一切监听器运用 func
。func
有必要回来一个 bool 值,若回来值为 false, forEachIf 将当即中止循环。
当一切监听器都被触发履行,或未找到 event
时回来 true
。当 func
回来 false
时回来 false
。
dispatch
void dispatch(Args ...args);
template <typename T>
void dispatch(T && first, Args ...args);
调度一个事情。dispatch
函数的参数是要被调度的事情类型。
一切的监听器都会被运用 args
参数调用。
该函数是同步的。一切监听器都会在调用 dispatch
的线程中被调用。
这两个重载函数略有区别,详细如何运用要根据 ArgumentPassingMode
战略而定。详情请阅览github.com/wqking/even… 文档。
嵌套监听器安全
- 若一个监听器在一次调度的进程中,向调度器中参加另一个有着相同事情类型的监听器,则新的监听器可以确保不会在本次时刻调度的进程中被调用。这是由一个 64 位无符号整数类型的计数器确保的,若在一次调度的进程中该计数器值溢出到 0 则会破坏该规矩,但该规矩将持续处理子序列调度。
- 一次调度进程中移除的一切监听器都不会被触发。
- 上面的两点在多线程中不建立。若在一个线程正在触发回调列表的时分,其他线程增加或移除了一个回调函数,则被增加或移除的这个回调函数或许会在本次触发履行的进程中履行。
时刻复杂度
此处讨论的时刻复杂度是操作回调列表中的监听器的复杂度,n
是监听器的数量。并不包含搜索 std::map
中事情所耗费的时刻,该部分的时刻复杂度为 O(log n) 。
-
appendListener
:O(1) -
prependListener
:O(1) -
insertListener
:O(1) -
removeListener
:O(1)
内部数据结构
EventDispatcher 运用 CallbackList 来办理监听器回调函数。
EventQueue (事情行列)类参考手册
目录
-
描绘
-
API 参考
- 头文件
- 模板参数
- 公共类型
- 成员函数
- 内部类 EventQueue::DisableQueueNotify
-
内部数据结构
描绘
EventQueue 在包含了 EventDispatcher 一切特性的根底上新增了事情行列特性。留意:EventQueue 并非派生自 EventDispatcher ,请勿尝试将 EventQueue 转换为 EventDispatcher 类型。
EventQueue 是异步的。事情会在调用 EventQueue::enqueue
时被缓存进 EventQueue 的事情行列,并在后续调用 EventQueue::process
时被调度。
EventQueue 相当于是 Qt 中的事情体系(QEvent),或 Windows API 中的信息处理(message processing)。
API 参考
头文件
eventpp/eventqueue.h
模板参数
template <
typename Event,
typename Prototype,
typename Policies = DefaultPolicies
>
class EventQueue;
EventQueue 的模板参数与 EventDispatcher 的模板参数完全一致。详细信息请参阅 EventDispatcher 文档。
公共类型
QueuedEvent
:存储在行列中的事情的数据类型。其声明的伪代码表示如下:
struct EventQueue::QueuedEvent
{
EventType event;
std::tuple<ArgTypes...> argument;
// 获取事情
EventType getEvent() const;
// 获取索引为 N 的实参
// 与 std::get<N>(queuedEvent.arguments) 相同
template <std::size_t N>
NthArgType getArgument() const;
};
event
是 EventQueue::Event , arguments
是 enqueue
中传递的实参。
成员函数
结构函数
EventQueue();
EventQueue(const EventQueue & other);
EventQueue(EventQueue && other) noexcept;
EventQueue & operator = (const EventQueue & other);
EventQueue & operator = (EventQueue && other) noexcept;
EventQueue 可以仿制、移动、赋值和移动赋值
留意:已排入行列的事情无法被仿制、移动、赋值和移动赋值,这些操作只会对监听器收效。这就意味着,已排入行列的事情不会在 EventQueue 被仿制和赋值时仿制。
enqueue
template <typename ...A>
void enqueue(A ...args);
template <typename T, typename ...A>
void enqueue(T && first, A ...args);
将一个事情参加事情行列。事情的类型包含在传给 enqueue
函数的实参中。
一切可仿制实参都会被仿制到内部的数据结构中。一切不行仿制但可移动的实参都会被移动。
EventQueue 的参数有必要满足可仿制和可移动两项中的一项。
假如界说的参数是基类的引证,却传入了一个派生类的目标,那么就只会保存基类,派生部分则会丢失。这种情况下一般可以运用同享指针来满足相关需求。
假如参数是指针,那么 EventQueue 就只会存储指针。该指针所指向的目标有必要在事情处理完毕之前都是可用的。
enqueue
会唤醒一切由 wait
或 waitFor
堵塞的线程。
该函数的时刻复杂度为 O(1) 。
这两个重载函数略有不同,详细的用法取决于 ArgumentPassingMode
战略。详情请阅览github.com/wqking/even… 文档。
process
bool process();
处理事情行列。一切事情行列中的事情都会被一次性调度,并从行列中移除。
若有事情被处理,该函数将回来 true 。回来 false 则代表未处理任何事情。
在哪个线程中调用了 process
,一切的监听器就会在哪个线程中履行。
在 process()
履行进程中新增加进行列的事情不会在当时的 process()
中被调度。
process()
能高效完结单线程事情处理,其会在当时线程中处理行列中的一切事情。若想在多个线程中高效处理事情,请运用 processOne()
。
留意:若 process()
被一同在多个线程中调用,事情行列中的事情也将只被处理一次。
processOne
bool processOne();
处理事情行列中的一个事情。该函数将会调度事情行列中的第一个事情,并将该事情移除行列。
若事情成功被处理,该函数回来 true ,不然回来 false 。
在哪个线程中调用了 processOne()
,监听器就会在哪个线程中履行。
在履行 processOne()
时被增加进行列的新事情将不会在当时的 processOne()
进程中被调度。
若有多个线程处理事情,processOne()
要比 process()
更高效,由于其能将事情处理涣散到不同的线程中履行。但若只有一个事情处理线程,则 process()
更高效。
留意:若 processOne()
被一同在多个线程中调用,那么事情行列中的事情也只会被处理一次。
processIf
template <typename Predictor>
bool processIf(Predictor && predictor);
处理事情行列。在处理一个事情前,该事情将先被传给 predictor
,仅当 predictor
回来 true 时,该事情才会被处理。若回来 false ,则会越过该事情持续处理后边的事情。被越过的事情则会被持续保留在行列中。
predictor
是一个可调用目标(函数, lambda 表达式等),其接纳的参数与 EventQueue::enqueue
接纳的参数一致或不接纳参数,回来值应为 bool 类型值。 eventpp 会正确地传递一切参数。若有事情被处理,该函数将回来 true 。回来 false 则代表未处理任何事情。
processIf
在下面这些场景中很有用:
- 在特定的线程中处理特定的事情。例如在 GUI 运用中,UI 相关事情只应该在主线程中处理。则在该场景中,
predictor
可以只对 UI 事情回来 true ,而对一切的非 UI 事情回来 false 。
processUntil
template <typename Predictor>
bool processUnitl(Predictor && predictor);
处理事情行列。在处理一个事情前,该事情将先被传给 predictor
,若其回来 true , processUnitl
将会当即中止事情处理并回来。若 predictor
回来 false ,则 processUntil
将持续处理事情。
predictor
是一个可调用目标(函数, lambda 表达式等),其接纳的参数与 EventQueue::enqueue
接纳的参数一致或不接纳参数,回来值应为 bool 类型值。 eventpp 会正确地传递一切参数。若有事情被处理,该函数将回来 true 。回来 false 则代表未处理任何事情。
processUnitl
可通过约束事情处理时刻来模仿“超时”。例如在游戏引擎中,一次事情处理时刻要被约束在几毫秒之内,没处理完的事情需求留到下一个循环中进行处理。该需求就可以通过让 predictor
在超时的时分回来 true 来完成。
emptyQueue
bool emptyQueue() const;
在事情行列中没有事情时回来 true ,不然回来 false 。
留意:在多线程环境下,空状况或许在该函数回来后立刻发生改变。
留意:不要用 while(!eventQueue.emptyQueue()) {}
的写法来写事情循环。由于编译器会内联代码,导致该循环永久查看不到空状况变化,从而造成死循环。安全的写法应该是 while(eventQueue.waitFor(std::chrono::nanoseconds(0)));
clearEvents
void clearEvents();
在不调度事情的情况下清空行列中的一切事情。
该函数可用于清空已排队事情中的引证(比方同享指针),以防止循环引证。
wait
void wait() const;
wait
将让当时线程持续堵塞,直至行列非空(参加了新的事情)。
留意:虽然 wait
在内部处理了假唤醒的问题,但并不能确保 wait
回来后行列非空。
wait
在运用一个线程处理事情行列时很有用,用法如下:
for(;;) {
eventQueue.wait();
eventQueue.process();
}
虽然上面的代码中不带 wait
也能正常运转,但那样做将浪费 CPU 功能。
waitFor
template <class Rep, class Period>
bool waitFor(const std::chrono::duration<Rep, Period> & duration) const;
等候不超过 duration
所指定的超时时刻。
当行列非空时回来 true ,当超时时回来 false 。
waitFor
在当事情行列处理线程需求做其他条件查看时很有用,例如:
std::atomic<bool> shouldStop(false);
for(;;) {
while(!eventQueue.waitFor(std::chrono::milliseconds(10) && !shouldStop.load());
if(shouldStop.load()) {
break;
}
eventQueue.process();
}
peekEvent
bool peekEvent(EventQueue::QueuedEvent * queuedEvent);
从事情行列中取出一个事情。事情将在 queuedEvent
中回来。
struct EventQueue::QueuedEvent
{
TheEventType event;
std::tuple<ArgumentTypes...> arguemnts;
};
queuedEvent
是一个 EventQueue::QueuedEvent 结构体。event
是 EventQueue::Event
,arguments
是 enqueue
中传入的参数。
该函数在事情行列为空时回来 false ,事情成功取回时回来 true 。
在函数回来后,原事情不会被移除,而会仍然留在行列中。
留意:peekEvent
无法和不行仿制的事情参数一同运用。若 peekEvent
在有不行仿制参数时被调用,会导致编译失利。
takeEvent
bool takeEvent(EventQueue::QueuedEvent * queuedEvent);
从事情行列中取走一个事情,并将该事情从事情行列中移除。事情将在 queuedEvent
中回来。
该函数在事情行列为空时回来 false ,事情成功取回时回来 true 。
在函数回来后,原来的事情将被从事情行列中移除。
留意:takeEvent
可以和不行仿制事情参数一同会用。
dispatch
void dispatch(const QueuedEvent & queuedEvent);
调度由 peekEvent
或 takeEvent
回来的事情。
内部类 EventQueue::DisableQueueNotify
EventQueue::DisableQueueNotify
是一个 RAII 类,其用于暂时防止事情行列唤醒等候的线程。当存在 DisableQueueNotify
目标时,调用 enqueue
不会唤醒任何由 wait
堵塞的线程。当离开 DisableQueueNotify
的效果域时,事情行列就从头可被唤醒了。若存在超过一个 DisableQueueNotify
目标,线程就只可以在一切的目标都被毁掉后才干从头可被唤醒。DisableQueueNotify
在需求批量向事情行列中参加事情时,可以有效提高功能。例如,在游戏引擎的主循环中,可以在一帧的开端时创立 DisableQueueNotify
,紧接着向行列中增加一系列事情,然后在这一帧的结尾毁掉 DisableQueueNotify
,开端处理这一帧中增加的一切事情。
DisableQueueNotify
的实例化,需求传入指向事情行列的指针。示例代码如下:
using EQ = eventpp::EventQueue<int, void()>;
EQ queue;
{
EQ::DisableQueueNotify disableNotify(&queue);
// 任何堵塞的线程都不会被下面的两行代码唤醒
queue.enqueue(1);
queue.enqueue(2);
}
// 任何堵塞的线程都会在此处被当即唤醒
// 由于这儿没有 DisableQueueNotify 实例,因此任何堵塞线程都会被下面这行代码唤醒
queue.enqueue(3);
内部数据结构
EventQueue 运用三个 std::list
来办理事情行列。
第一个忙列表( busy list )保护已入列事情的一切节点。
第二个等候列表( idle list )保护一切等候中的节点。在一个事情完结调度并被从行列中移除后,EventQueue 将把没有用过的节点移入等候列表,而不是直接释放相应的内存。这可以改进功能并防止产生内存碎片。
第三个列表是在 process()
函数中运用的本地暂时列表( local temporary list )。在一次处理的进程中,忙列表会被交流( swap )到暂时列表,一切事情都是在暂时列表中被调度的。在这之后,暂时列表会被回来,并追加到等候列表中。