前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Boost C++ 库 | 事件处理

Boost C++ 库 | 事件处理

原创
作者头像
Qt历险记
发布2024-10-21 20:37:10
发布2024-10-21 20:37:10
10000
代码可运行
举报
文章被收录于专栏:C++C++
运行总次数:0
代码可运行

​点击上方"蓝字"关注我们

01、概述

>>>很多开发者在听到术语'事件处理'时就会想到GUI:点击一下某个按钮,相关联的功能就会被执行。点击本身就是事件,而功能就是相对应的事件处理器。例如Qt 这一模式的使用当然不仅限于GUI。一般情况下,任意对象都可以调用基于特定事件的专门函数。本章所介绍的 Boost.Signals 库提供了一个简单的方法在 C++ 中应用这一模式。 严格来说,Boost.Function 库也可以用于事件处理。不过,Boost.Function 和 Boost.Signals 之间的一个主要区别在于,Boost.Signals 能够将一个以上的事件处理器关联至单个事件。因此,Boost.Signals 可以更好地支持事件驱动的开发,当需要进行事件处理时,应作为第一选择。

02、信号Signals

>>>虽然这个库的名字乍一看好象有点误导,但实际上并非如此。Boost.Signals 所实现的模式被命名为 '信号至插槽' (signal to slot),它基于以下概念:当对应的信号被发出时,相关联的插槽即被执行。原则上,你可以把单词 '信号' 和 '插槽' 分别替换为 '事件' 和 '事件处理器'。不过,由于信号可以在任意给定的时间发出,所以这一概念放弃了 '事件' 的名字。 因此,Boost.Signals 没有提供任何类似于 '事件' 的类。相反,它提供了一个名为 boost::signal 的类,定义于 boost/signal.hpp. 实际上,这个头文件是唯一一个需要知道的,因为它会自动包含其它相关的头文件。 Boost.Signals 定义了其它一些类,位于 boost::signals 名字空间中。由于 boost::signal 是最常被用到的类,所以它是位于名字空间 boost 中的。

代码语言:javascript
代码运行次数:0
复制
#include <boost/signal.hpp> #include <iostream> ​void func() {   std::cout << "Hello, world!" << std::endl; } ​int main() {   boost::signal<void ()> s;    s.connect(func);   s(); } ​// 05:36:46: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...Hello, world!

>>>boost::signal 实际上被实现为一个模板函数,具有被用作为事件处理器的函数的签名,该签名也是它的模板参数。在这个例子中,只有签名为 void () 的函数可以被成功关联至信号 s。 函数 func() 被通过 connect() 方法关联至信号 s。由于 func() 符合所要求的 void () 签名,所以该关联成功建立。因此当信号 s 被触发时,func() 将被调用。 信号是通过调用 s 来触发的,就象普通的函数调用那样。这个函数的签名对应于作为模板参数传入的签名:因为 void () 不要求任何参数,所以括号内是空的。 调用 s 会引发一个触发器,进而执行相应的 func() 函数 - 之前用 connect() 关联了的。 同一例子也可以用 Boost.Function 来实现。 (和前一个例子相类似,func() 被关联至 f。当 f 被调用时,就会相应地执行 func()。Boost.Function 仅限于这种情形下适用,而 Boost.Signals 则提供了多得多的方式,如关联多个函数至单个特定信号,示例如下。)

代码语言:javascript
代码运行次数:0
复制
#include <boost/signals2.hpp>#include <iostream>#include <boost/function.hpp>​void func() {    std::cout << "111" << std::endl;}int main(){    boost::signals2::signal<void ()>s;    s.connect(func);    s(); // 触发​    boost::function<void ()>f;    f = func;    f(); // 函数指针}​// 05:36:46: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...111
代码语言:javascript
代码运行次数:0
复制
#include <boost/signals2.hpp>#include <iostream>#include <boost/function.hpp>​void func1() {    std::cout << "1" << std::endl;}​void func2() {    std::cout << "2" << std::endl;}​int main(){    boost::signals2::signal<void ()>s;    s.connect(func1);    s.connect(func2);}​// 05:36:46: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...12

>>>boost::signal 可以通过反复调用 connect() 方法来把多个函数赋值给单个特定信号。当该信号被触发时,这些函数被按照之前用 connect() 进行关联时的顺序来执行。 另外,执行的顺序也可通过 connect() 方法的另一个重载版本来明确指定,该重载版本要求以一个 int 类型的值作为额外的参数。

代码语言:javascript
代码运行次数:0
复制
#include <boost/signals2.hpp>#include <iostream>#include <boost/function.hpp>​void func1() {    std::cout << "func1" << std::endl;}​void func2() {    std::cout << "func2" << std::endl;}​void func3() {    std::cout << "func3" << std::endl;}​int main(){    boost::signals2::signal<void ()>s;    s.connect(2,func1); // 先执行3    s.connect(1,func2); // 先执行2    s.connect(0,func3); // 先执行1    s();}​// 05:40:11: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...func3func2func1

>>>这个例子仅输出 Hello,因为与 func2() 的关联在触发信号之前已经被释放。 除了 connect() 和 disconnect() 以外,boost::signal 还提供了几个方法。

代码语言:javascript
代码运行次数:0
复制
#include <boost/signals2.hpp>#include <iostream>#include <boost/function.hpp>​void func1() {    std::cout << "func1" << std::endl;}​void func2() {    std::cout << "func2" << std::endl;}​void func3() {    std::cout << "func3" << std::endl;}​int main(){    boost::signals2::signal<void ()>s;    s.connect(2,func1);    s.connect(1,func2);    s.connect(0,func3);    s.disconnect(func3);    s();}​// 05:41:52: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...func2func1
代码语言:javascript
代码运行次数:0
复制
#include <boost/signals2.hpp>#include <iostream>#include <boost/function.hpp>​void func1() {    std::cout << "func1" << std::endl;}​void func2() {    std::cout << "func2" << std::endl;}​void func3() {    std::cout << "func3" << std::endl;}​int main(){    boost::signals2::signal<void ()>s;    s.connect(2,func1);    s.connect(1,func2);    s.connect(0,func3);    std::cout << s.num_slots() << std::endl;    s.disconnect(func3);​    if (!s.empty()) {        s();        s.disconnect_all_slots();    }​    std::cout << "================" << std::endl;    s(); // 已断开}​// 05:45:54: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...3func2func1================

>>>num_slots() 返回已关联函数的数量。如果没有函数被关联,则 num_slots() 返回0。在这种特定情况下,可以用 empty() 方法来替代。 disconnect_all_slots() 方法所做的实际上正是它的名字所表达的:释放所有已有的关联。 看完了函数如何被关联至信号,以及弄明白了信号被触发时会发生什么事之后,还有一个问题:这些函数的返回值去了哪里?以下例子回答了这个问题。

代码语言:javascript
代码运行次数:0
复制
#include <boost/signals2.hpp>#include <iostream>#include <boost/function.hpp>#include <boost/optional.hpp>#include <boost/optional/optional_io.hpp> // 添加这一行std::cout << s() << std::endl;​​int func1() {    return 1;}​int func2() {    return 2;}​int main(){    boost::signals2::signal<int ()> s;    s.connect(func1);    s.connect(func2);    std::cout << s() << std::endl;}​// 05:56:06: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost... 2

>>>func1() 和 func2() 都具有 int 类型的返回值。 s 将处理两个返回值,并将它们都写出至标准输出流。那么,到底会发生什么呢? 以上例子实际上会把 2 写出至标准输出流。两个返回值都被 s 正确接收,但除了最后一个值,其它值都会被忽略。缺省情况下,所有被关联函数中,实际上只有最后一个返回值被返回。 你可以定制一个信号,令每个返回值都被相应地处理。为此,要把一个称为合成器(combiner)的东西作为第二个参数传递给 boost::signal

代码语言:javascript
代码运行次数:0
复制
#include <boost/signals2.hpp>  // 引入Boost库的信号与插槽功能#include <iostream>            // 引入输入输出流库#include <boost/function.hpp>  // 引入Boost库的函数对象功能#include <boost/optional.hpp>  // 引入Boost库的可选类型#include <boost/optional/optional_io.hpp> // 引入Boost库的可选类型的输入输出操作(用于打印可选值)​// 定义返回值为int的函数func1int func1() {    return 1;  // 返回1}​// 定义返回值为int的函数func2int func2() {    return 2;  // 返回2}​// 模板结构体minValue,用于返回给定范围内的最小值template <typename T>struct minValue{    typedef T result_type;  // 定义结果类型为模板参数T    template <typename Input_iterator>    T operator() (Input_iterator first, Input_iterator last) const {        return *std::min_element(first, last);  // 使用std::min_element找到首尾迭代器间的最小值并返回    }};​int main(){    // 创建一个信号s,返回类型为int,使用minValue<int>来决定信号的结果    boost::signals2::signal<int (), minValue<int>> s;    s.connect(func1);  // 将func1连接到信号s    s.connect(func2);  // 将func2连接到信号s    std::cout << s() << std::endl;  // 调用信号s并输出结果,应该输出最小值}​// 06:15:15: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...1

>>>合成器是一个重载了 operator()() 操作符的类。这个操作符会被自动调用,传入两个迭代器,指向某个特定信号的所有返回值。以上例子使用了标准 C++ 算法 std::min_element() 来确定并返回最小的值。 不幸的是,我们不可能把象 std::min_element() 这样的一个算法直接传给 boost::signal 作为一个模板参数。 boost::signal 要求这个合成器定义一个名为 result_type 的类型,用于说明 operator()() 操作符返回值的类型。由于在标准 C++ 算法中缺少这个类型,所以在编译时会产生一个相应的错误。 除了对返回值进行分析以外,合成器也可以保存它们。 (这个例子把所有返回值保存在一个 vector 中,再由 s() 返回。)

代码语言:javascript
代码运行次数:0
复制
#include <boost/signals2/signal.hpp>  // 引入Boost库的信号机制#include <iostream>                    // 引入输入输出流库#include <vector>                      // 引入向量(动态数组)库#include <algorithm>                   // 引入算法库(例如std::min_element)​// 定义返回int的函数func1int func1(){    return 1;  // 返回值1}​// 定义返回int的函数func2int func2(){    return 2;  // 返回值2}​// 模板结构体min_element,用于生成包含元素的容器template <typename T>struct min_element{    typedef T result_type;  // 定义结果类型为模板参数T​    template <typename InputIterator>    T operator()(InputIterator first, InputIterator last) const{        return T(first, last);  // 创建一个类型为T的对象,使用迭代器范围[first, last)初始化    }};​int main(){    // 创建一个信号s,返回类型为int,使用min_element<std::vector<int>>作为信号的聚合类型    boost::signals2::signal<int (), min_element<std::vector<int>>> s;    s.connect(func1);  // 将func1连接到信号s    s.connect(func2);  // 将func2连接到信号s​    // 调用信号s并将返回的std::vector<int>赋值给v    std::vector<int> v = s();​    // 计算并输出v中最小的元素    std::cout << *std::min_element(v.begin(), v.end()) << std::endl;​    // 遍历v并输出每个元素    for (int var : v) {        std::cout << var << std::endl;    }}​// 06:22:29: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...112

09、连接Connections

>>>函数可以通过由 boost::signal 所提供的 connect() 和 disconnect() 方法的帮助来进行管理。由于 connect() 会返回一个类型为 boost::signals::connection 的值,它们可以通过其它方法来管理。

代码语言:javascript
代码运行次数:0
复制
#include <boost/signals2/signal.hpp>  // 引入Boost库的信号机制#include <iostream>                    // 引入输入输出流库#include <vector>                      // 引入向量(动态数组)库#include <algorithm>                   // 引入算法库(例如std::min_element)​// 定义一个无返回值的函数funcvoid func(){    std::cout << "Hello, world!" << std::endl;  // 输出"Hello, world!"到控制台}​int main(){    // 创建一个无参数、无返回值的信号s    boost::signals2::signal<void ()> s;​    // 连接函数func到信号s,并返回连接对象c    boost::signals2::connection c = s.connect(func);​    // 触发信号s,调用连接的函数func    s();​    // 断开连接对象c,即从信号s中断开func函数的连接    c.disconnect();​    // 输出一个空行    std::cout << "" << std::endl;}​

>>>boost::signal 的 disconnect() 方法需要传入一个函数指针,而直接调用 boost::signals::connection 对象上的 disconnect() 方法则略去该参数。 除了 disconnect() 方法之外,boost::signals::connection 还提供了其它方法,如 block() 和 unblock()

代码语言:javascript
代码运行次数:0
复制
#include <boost/signal.hpp>  // 引入Boost库的信号机制(注意:此为旧版Boost,建议使用boost/signals2/signal.hpp)#include <iostream>          // 引入输入输出流库​// 定义一个无返回值的函数funcvoid func() {     std::cout << "Hello, world!" << std::endl;  // 输出"Hello, world!"到控制台} ​int main() {     // 创建一个无参数、无返回值的信号s    boost::signal<void ()> s;         // 连接函数func到信号s,并返回连接对象c    boost::signals::connection c = s.connect(func);         // 阻止信号s调用连接的函数(即func),在此时调用信号不会触发func    c.block();         // 调用信号s,由于之前已经阻止,func不会被调用,什么也不会输出    s();         // 解除阻止,使得信号s可以再次调用连接的函数    c.unblock();         // 再次调用信号s,此时会触发func,输出"Hello, world!"    s(); }​

>>>以上程序只会执行一次 func()。 虽然信号 s 被触发了两次,但是在第一次触发时 func() 不会被调用,因为连接 c 实际上已经被 block() 调用所阻塞。 由于在第二次触发之前调用了 unblock(),所以之后 func() 被正确地执行。 除了 boost::signals::connection 以外,还有一个名为 boost::signals::scoped_connection 的类,它会在析构时自动释放连接。

代码语言:javascript
代码运行次数:0
复制
#include <boost/signal.hpp>  // 引入Boost库的信号机制(注意:此为旧版Boost,建议使用boost/signals2/signal.hpp)#include <iostream>          // 引入输入输出流库​// 定义一个无返回值的函数funcvoid func() {     std::cout << "Hello, world!" << std::endl;  // 输出"Hello, world!"到控制台} ​int main() {     // 创建一个无参数、无返回值的信号s    boost::signal<void ()> s;         {         // 创建一个作用域,以便在此作用域内连接func到信号s,并返回一个scoped_connection对象c        boost::signals::scoped_connection c = s.connect(func);     } // 作用域结束,连接对象c超出作用域并析构,自动断开连接​    // 调用信号s,此时由于连接已经被断开,func不会被调用,什么也不会输出    s(); }​

>>>boost::signals::scoped_connection 实际上是派生自 boost::signals::connection 的,所以它提供了相同的方法。它们之间的区别仅在于,在析构 boost::signals::scoped_connection 时,连接会自动释放。 虽然 boost::signals::scoped_connection 的确令自动释放连接更为容易,但是该类型的对象仍需要管理。 如果在其它情形下连接也可以被自动释放,而且不需要管理这些对象的话,就更好了。

代码语言:javascript
代码运行次数:0
复制
#include <boost/signals2/signal.hpp>  // 引入Boost库的信号机制#include <iostream>                    // 引入输入输出流库#include <boost/bind.hpp>              // 引入Boost库的bind功能,用于将成员函数与对象绑定#include <memory>                      // 引入内存管理库,包括智能指针​// 定义一个类world,继承自boost::signals2::trackableclass world : public boost::signals2::trackable{public:    // 定义一个成员函数hello,用于输出"Hello, world!"    void hello() const{        std::cout << "Hello, world!" << std::endl; // 输出"Hello, world!"到控制台    }};​// 定义一个无返回值的全局函数funcvoid func(){    std::cout << "Hello, world!" << std::endl;  // 输出"Hello, world!"到控制台}​int main(){    // 创建一个无参数、无返回值的信号s    boost::signals2::signal<void ()> s;​    {        // 使用std::auto_ptr创建一个world对象w(注意:std::auto_ptr是已过时的,建议使用std::unique_ptr)        std::auto_ptr<world> w(new world());        // 将world类的成员函数hello与信号s连接        s.connect(boost::bind(&world::hello, w.get()));    }​    // 输出当前信号s中连接的槽数量    std::cout << s.num_slots() << std::endl;​    s();}​

>>>这节的内容很想Qt的信号与槽,以后C++发是不是就简单多了。。。 温故而知新

Qt | ubuntu20.04+boost_1_86_0搭建和编译(使用Qt6.5.3中运行测试程序)

Boost C++ 库 | 是什么?

Boost C++ 库 | 智能指针(RAII、作用域指针、作用域数组)

Boost C++ 库 | 智能指针(共享指针、共享数组、弱指针、介入式指针、指针容器)入门

Boost C++ 库 | 函数对象(数百家企业面试题C++分享)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ​点击上方"蓝字"关注我们
  • 01、概述
  • 02、信号Signals
  • 09、连接Connections
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档