【BOOST C++ 20 设计模式】(2)库Boost.Signals2

news/2024/7/1 2:28:12

一、关于Boost.Signals2

       Boost.Signals2 实现了信号/槽的概念。一个或多个函数(称为槽)与可以发出信号的对象相关联。每次发出信号时,都会调用链接的函数。

        信号/槽概念在开发具有图形用户界面的应用程序时非常有用。可以对按钮进行建模,以便在用户单击它们时发出信号。它们可以支持指向许多函数的链接以处理用户输入。这样就可以灵活地处理事件。

        std::function 也可用于事件处理。 std::function 和 Boost.Signals2 之间的一个重要区别是 Boost.Signals2 可以将多个事件处理程序与单个事件相关联。因此,Boost.Signals2更适合支持事件驱动开发,应该是任何需要处理事件的首选。

        Boost.Signals2 继承了库 Boost.Signals,后者已被弃用且本书未讨论。

 Table of Contents

  • Signals
  • Connections
  • Multithreading

二、关于Signals库

        Boost.Signals2 提供类 boost::signals2::signal,可用于创建信号。此类在 boost/signals2/signal.hpp 中定义。或者,您可以使用头文件 boost/signals2.hpp,这是一个主头文件,定义了 Boost.Signals2 中可用的所有类和函数。

        Boost.Signals2 定义了 boost::signals2::signal 和其他类,以及命名空间 boost::signals2 中的所有函数。

        示例 67.1。 “你好世界!”使用 boost::signals2::signal

#include <boost/signals2.hpp>
#include <iostream>

using namespace boost::signals2;

int main()
{
  signal<void()> s;
  s.connect([]{ std::cout << "Hello, world!\n"; });
  s();
}

        boost::signals2::signal 是一个类模板,它期望将用作事件处理程序的函数的签名作为模板参数。在示例 67.1 中,只有签名为 void() 的函数才能与信号相关联。

        lambda 函数通过 connect() 与信号 s 相关联。因为 lambda 函数符合所需的签名 void(),所以关联已成功建立。每当触发信号 s 时,都会调用 lambda 函数。

        信号是通过像调用常规函数一样调用 s 来触发的。此函数的签名与作为模板参数传递的签名匹配。括号是空的,因为 void() 不需要任何参数。调用 s 会产生一个触发器,而该触发器又会执行之前与 connect() 相关联的 lambda 函数。

        示例 67.1 也可以使用 std::function 实现,如示例 67.2 所示。

        示例 67.2。 “你好世界!”使用 std::function

#include <functional>
#include <iostream>

int main()
{
  std::function<void()> f;
  f = []{ std::cout << "Hello, world!\n"; };
  f();
}

        在示例 67.2 中,调用 f 时也会执行 lambda 函数。虽然 std::function 只能用于类似示例 67.2 的场景,但 Boost.Signals2 提供了更多种类。例如,它可以将多个函数与特定信号相关联(参见示例 67.3)。

        示例 67.3。带有 boost::signals2::signal 的多个事件处理程序

#include <boost/signals2.hpp>
#include <iostream>

using namespace boost::signals2;

int main()
{
  signal<void()> s;
  s.connect([]{ std::cout << "Hello"; });
  s.connect([]{ std::cout << ", world!\n"; });
  s();
}

        boost::signals2::signal 允许您通过重复调用 connect() 将多个函数分配给特定信号。每当触发信号时,函数都会按照它们与 connect() 关联的顺序执行。

        顺序也可以在 connect() 的重载版本的帮助下明确定义,它需要一个 int 类型的值作为附加参数(示例 67.4)。

        示例 67.4。具有明确顺序的事件处理程序

#include <boost/signals2.hpp>
#include <iostream>

using namespace boost::signals2;

int main()
{
  signal<void()> s;
  s.connect(1, []{ std::cout << ", world!\n"; });
  s.connect(0, []{ std::cout << "Hello"; });
  s();
}

与前面的示例一样,示例 67.4 显示 Hello, world!。

要从信号中释放关联函数,请调用 disconnect()。

示例 67.5。断开事件处理程序与 boost::signals2::signal 的连接

#include <boost/signals2.hpp>
#include <iostream>

using namespace boost::signals2;

void hello() { std::cout << "Hello"; }
void world() { std::cout << ", world!\n"; }

int main()
{
  signal<void()> s;
  s.connect(hello);
  s.connect(world);
  s.disconnect(world);
  s();
}

Example 67.5 

        示例 67.5 仅打印 Hello,因为与 world() 的关联在信号被触发之前已释放。

        除了 connect() 和 disconnect() 之外,boost::signals2::signal 还提供了几个成员函数(参见示例 67.6)。

        示例 67.6。 boost::signals2::signal 的附加成员函数

#include <boost/signals2.hpp>
#include <iostream>

using namespace boost::signals2;

int main()
{
  signal<void()> s;
  s.connect([]{ std::cout << "Hello"; });
  s.connect([]{ std::cout << ", world!"; });
  std::cout << s.num_slots() << '\n';
  if (!s.empty())
    s();
  s.disconnect_all_slots();
}

        num_slots() 返回关联函数的数量。如果没有函数关联,num_slots() 返回 0。empty() 告诉您事件处理程序是否已连接。而 disconnect_all_slots() 的作用正如它的名字所说:它释放所有现有的关联。

        示例 67.7。处理事件处理程序的返回值

#include <boost/signals2.hpp>
#include <iostream>

using namespace boost::signals2;

int main()
{
  signal<int()> s;
  s.connect([]{ return 1; });
  s.connect([]{ return 2; });
  std::cout << *s() << '\n';
}

        在示例 67.7 中,两个 lambda 函数与信号关联。第一个 lambda 函数返回 1,第二个返回 2。

        示例 67.7 将 2 写入标准输出。 s 正确接受了两个返回值,但除了最后一个之外的所有返回值都被忽略了。默认情况下,只返回所有关联函数的最后一个返回值。

        请注意,s() 不会直接返回上次调用的函数的结果。返回类型为 boost::optional 的对象,取消引用时返回数字 2。触发与任何函数无关的信号不会产生任何返回值。因此,在这种情况下,boost::optional 允许 Boost.Signals2 返回一个空对象。 boost::optional 在第 21 章中介绍。

        可以自定义信号,以便相应地处理各个返回值。为此,必须将组合器作为第二个模板参数传递给 boost::signals2::signal。

        示例 67.8。使用用户定义的组合器查找最小返回值

#include <boost/signals2.hpp>
#include <vector>
#include <algorithm>
#include <iostream>

using namespace boost::signals2;

template <typename T>
struct min_element
{
  typedef T result_type;

  template <typename InputIterator>
  T operator()(InputIterator first, InputIterator last) const
  {
    std::vector<T> v(first, last);
    return *std::min_element(v.begin(), v.end());
  }
};

int main()
{
  signal<int(), min_element<int>> s;
  s.connect([]{ return 1; });
  s.connect([]{ return 2; });
  std::cout << s() << '\n';
}

        组合器是一个具有重载的 operator() 的类。该运算符使用两个迭代器自动调用,这两个迭代器用于访问与特定信号关联的函数。当迭代器被取消引用时,函数被调用并且它们的返回值在组合器中变得可用。然后可以使用标准库中的常用算法,例如 std::min_element() 来计算并返回最小值(参见示例 67.8)。

        boost::signals2::signal 使用 boost::signals2::optional_last_value 作为默认组合器。此组合器返回 boost::optional 类型的对象。用户可以定义一个具有任何类型返回值的组合器。例如,示例 67.8 中的组合器 min_element 返回作为模板参数传递给 min_element 的类型。

        无法将诸如 std::min_element() 之类的算法作为模板参数直接传递给 boost::signals2::signal。 boost::signals2::signal 期望组合器定义一个名为 result_type 的类型,它表示 operator() 返回值的类型。由于这个类型没有被标准算法定义,所以编译器会报错。

        请注意,不可能将迭代器 first 和 last 直接传递给 std::min_element() ,因为该算法需要前向迭代器,而组合器使用输入迭代器。这就是为什么在使用 std::min_element() 确定最小值之前使用向量存储所有返回值的原因。

        示例 67.9 修改组合器以将所有返回值存储在容器中,而不是评估它们。它将所有返回值存储在一个向量中,然后由 s() 返回该向量。

        示例 67.9。使用用户定义的组合器接收所有返回值

#include <boost/signals2.hpp>
#include <vector>
#include <algorithm>
#include <iostream>

using namespace boost::signals2;

template <typename T>
struct return_all
{
  typedef T result_type;

  template <typename InputIterator>
  T operator()(InputIterator first, InputIterator last) const
  {
    return T(first, last);
  }
};

int main()
{
  signal<int(), return_all<std::vector<int>>> s;
  s.connect([]{ return 1; });
  s.connect([]{ return 2; });
  std::vector<int> v = s();
  std::cout << *std::min_element(v.begin(), v.end()) << '\n';
}

练习

        创建一个带有课程按钮的程序。该类应代表图形用户界面中的按钮。添加成员函数 add_handler() 和 remove_handler() ,它们都希望传递一个函数。如果调用另一个名为 click() 的成员函数,则应依次调用已注册的处理程序。通过注册一个将消息写入标准输出的处理程序来实例化按钮并测试该类。调用 click() 来模拟鼠标点击按钮。


http://lihuaxi.xjx100.cn/news/307448.html

相关文章

Linux系统移植三:移植Kernel生成zImage和dtb文件

Linux系统移植系列 Linux系统移植一&#xff1a;移植U-BOOT 添加自己的板子并编译&#xff08;非petalinux版&#xff09; Linux系统移植二&#xff1a;生成fsbl引导文件并制作BOOT.bin 下载源码包 Xilinx官方linux源码包下载地址&#xff1a;https://github.com/Xilinx/lin…

微服务框架 SpringCloud微服务架构 12 DockerCompose 12.2 部署微服务集群

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构12 DockerCompose12.2 部署微服务集群12.2.1 直接开干12 DockerCompose 1…

阿里云部署应用

安装jdk 查看已安装版本 rpm -qa | grep java yum命令查找JDK1.8软件包 yum -y list java-1.8* 安装列表中的JDK1.8软件包 yum -y install java-1.8.0-openjdk-devel.x86_64 java -version 配置环境变量 vim /etc/profile JAVA_HOME/usr/lib/jvm/java-1.8.0-openjdk-1.8.…

51单片机自学报告--实验部分

微机接口技术-自主学习笔记 PPT链接&#xff1a;微机接口自学--51单片机自学汇报PPT_猫猫爱吃小鱼的博客-CSDN博客 效果演示gif: 四、Proteus仿真 仿真环境&#xff1a;电路仿真软件: Proteus HEX可执行文件编写软件: keil uVision4 keil uVision4新…

设计模型之单例设计

前言 单例模式&#xff08;Singleton Pattern&#xff09;是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。 这种模式涉及到一个单一的类&#xff0c;该类负责创建自己的对象&#xff0c;同时确保只有单个对象被…

【蓝桥杯】第十四届模拟赛第一期及第二期填空汇总

目录 1.A题&#xff08;进制位数&#xff09; 位运算符 第一期 问题描述 解析 第二期 解析 代码 2.B题&#xff08;日期问题&#xff09; 第一期 问题描述 解析 代码实现 执行结果 第二期 问题描述 解析 3.C题&#xff08;数学问题&#xff09; 第一期 问题…

4. 死信队列

二八佳人体似酥&#xff0c;腰间仗剑斩愚夫。虽然不见人头落&#xff0c;暗里教君骨髓枯。 死信 概念 先从概念解释上搞清楚这个定义&#xff0c;死信&#xff0c;顾名思义就是无法被消费的消息&#xff0c;字面意思可以这样理 解&#xff0c;一般来说&#xff0c;producer 将…

第十二章 Amortized Analysis平摊分析

第12章 Amortized Analysis平摊分析第10周 记于2022/11/29概率分析与平摊分析的区别概率分析平均执行时间考虑同一算法的所有可能输入情况 如果使用概率,则称为期望运行时间 针对单一操作/算法平摊分析针对某一数据结构的 操作序列 不使用概率 操作序列中的平均操作性能/代价【…