亲宝软件园·资讯

展开

C++ Boost.Signals2信号/槽概念

无水先生 人气:0

一、关于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() 来模拟鼠标点击按钮。

加载全部内容

相关教程
猜你喜欢
用户评论