• 4.2. 信号 Signals

    4.2. 信号 Signals

    虽然这个库的名字乍一看好象有点误导,但实际上并非如此。 Boost.Signals 所实现的模式被命名为 '信号至插槽' (signal to slot),它基于以下概念:当对应的信号被发出时,相关联的插槽即被执行。 原则上,你可以把单词 '信号' 和 '插槽' 分别替换为 '事件' 和 '事件处理器'。 不过,由于信号可以在任意给定的时间发出,所以这一概念放弃了 '事件' 的名字。

    因此,Boost.Signals 没有提供任何类似于 '事件' 的类。 相反,它提供了一个名为 boost::signal 的类,定义于 boost/signal.hpp. 实际上,这个头文件是唯一一个需要知道的,因为它会自动包含其它相关的头文件。

    Boost.Signals 定义了其它一些类,位于 boost::signals 名字空间中。 由于 boost::signal 是最常被用到的类,所以它是位于名字空间 boost 中的。

    1. #include <boost/signal.hpp>
    2. #include <iostream>
    3.  
    4. void func()
    5. {
    6. std::cout << "Hello, world!" << std::endl;
    7. }
    8.  
    9. int main()
    10. {
    11. boost::signal<void ()> s;
    12. s.connect(func);
    13. s();
    14. }
    • 下载源代码

    boost::signal 实际上被实现为一个模板函数,具有被用作为事件处理器的函数的签名,该签名也是它的模板参数。 在这个例子中,只有签名为 void () 的函数可以被成功关联至信号 s

    函数 func() 被通过 connect() 方法关联至信号 s。 由于 func() 符合所要求的 void () 签名,所以该关联成功建立。因此当信号 s 被触发时,func() 将被调用。

    信号是通过调用 s 来触发的,就象普通的函数调用那样。 这个函数的签名对应于作为模板参数传入的签名:因为 void () 不要求任何参数,所以括号内是空的。

    调用 s 会引发一个触发器,进而执行相应的 func() 函数 - 之前用 connect() 关联了的。

    同一例子也可以用 Boost.Function 来实现。

    1. #include <boost/function.hpp>
    2. #include <iostream>
    3.  
    4. void func()
    5. {
    6. std::cout << "Hello, world!" << std::endl;
    7. }
    8.  
    9. int main()
    10. {
    11. boost::function<void ()> f;
    12. f = func;
    13. f();
    14. }
    • 下载源代码

    和前一个例子相类似,func() 被关联至 f。 当 f 被调用时,就会相应地执行 func()。 Boost.Function 仅限于这种情形下适用,而 Boost.Signals 则提供了多得多的方式,如关联多个函数至单个特定信号,示例如下。

    1. #include <boost/signal.hpp>
    2. #include <iostream>
    3.  
    4. void func1()
    5. {
    6. std::cout << "Hello" << std::flush;
    7. }
    8.  
    9. void func2()
    10. {
    11. std::cout << ", world!" << std::endl;
    12. }
    13.  
    14. int main()
    15. {
    16. boost::signal<void ()> s;
    17. s.connect(func1);
    18. s.connect(func2);
    19. s();
    20. }
    • 下载源代码

    boost::signal 可以通过反复调用 connect() 方法来把多个函数赋值给单个特定信号。 当该信号被触发时,这些函数被按照之前用 connect() 进行关联时的顺序来执行。

    另外,执行的顺序也可通过 connect() 方法的另一个重载版本来明确指定,该重载版本要求以一个 int 类型的值作为额外的参数。

    1. #include <boost/signal.hpp>
    2. #include <iostream>
    3.  
    4. void func1()
    5. {
    6. std::cout << "Hello" << std::flush;
    7. }
    8.  
    9. void func2()
    10. {
    11. std::cout << ", world!" << std::endl;
    12. }
    13.  
    14. int main()
    15. {
    16. boost::signal<void ()> s;
    17. s.connect(1, func2);
    18. s.connect(0, func1);
    19. s();
    20. }
    • 下载源代码

    和前一个例子一样,func1()func2() 之前执行。

    要释放某个函数与给定信号的关联,可以用 disconnect() 方法。

    1. #include <boost/signal.hpp>
    2. #include <iostream>
    3.  
    4. void func1()
    5. {
    6. std::cout << "Hello" << std::endl;
    7. }
    8.  
    9. void func2()
    10. {
    11. std::cout << ", world!" << std::endl;
    12. }
    13.  
    14. int main()
    15. {
    16. boost::signal<void ()> s;
    17. s.connect(func1);
    18. s.connect(func2);
    19. s.disconnect(func2);
    20. s();
    21. }
    • 下载源代码

    这个例子仅输出 Hello,因为与 func2() 的关联在触发信号之前已经被释放。

    除了 connect()disconnect() 以外,boost::signal 还提供了几个方法。

    1. #include <boost/signal.hpp>
    2. #include <iostream>
    3.  
    4. void func1()
    5. {
    6. std::cout << "Hello" << std::flush;
    7. }
    8.  
    9. void func2()
    10. {
    11. std::cout << ", world!" << std::endl;
    12. }
    13.  
    14. int main()
    15. {
    16. boost::signal<void ()> s;
    17. s.connect(func1);
    18. s.connect(func2);
    19. std::cout << s.num_slots() << std::endl;
    20. if (!s.empty())
    21. s();
    22. s.disconnect_all_slots();
    23. }
    • 下载源代码

    num_slots() 返回已关联函数的数量。如果没有函数被关联,则 num_slots() 返回0。 在这种特定情况下,可以用 empty() 方法来替代。 disconnect_all_slots() 方法所做的实际上正是它的名字所表达的:释放所有已有的关联。

    看完了函数如何被关联至信号,以及弄明白了信号被触发时会发生什么事之后,还有一个问题:这些函数的返回值去了哪里? 以下例子回答了这个问题。

    1. #include <boost/signal.hpp>
    2. #include <iostream>
    3.  
    4. int func1()
    5. {
    6. return 1;
    7. }
    8.  
    9. int func2()
    10. {
    11. return 2;
    12. }
    13.  
    14. int main()
    15. {
    16. boost::signal<int ()> s;
    17. s.connect(func1);
    18. s.connect(func2);
    19. std::cout << s() << std::endl;
    20. }
    • 下载源代码

    func1()func2() 都具有 int 类型的返回值。 s 将处理两个返回值,并将它们都写出至标准输出流。 那么,到底会发生什么呢?

    以上例子实际上会把 2 写出至标准输出流。 两个返回值都被 s 正确接收,但除了最后一个值,其它值都会被忽略。 缺省情况下,所有被关联函数中,实际上只有最后一个返回值被返回。

    你可以定制一个信号,令每个返回值都被相应地处理。 为此,要把一个称为合成器(combiner)的东西作为第二个参数传递给 boost::signal

    1. #include <boost/signal.hpp>
    2. #include <iostream>
    3. #include <algorithm>
    4.  
    5. int func1()
    6. {
    7. return 1;
    8. }
    9.  
    10. int func2()
    11. {
    12. return 2;
    13. }
    14.  
    15. template <typename T>
    16. struct min_element
    17. {
    18. typedef T result_type;
    19.  
    20. template <typename InputIterator>
    21. T operator()(InputIterator first, InputIterator last) const
    22. {
    23. return *std::min_element(first, last);
    24. }
    25. };
    26.  
    27. int main()
    28. {
    29. boost::signal<int (), min_element<int> > s;
    30. s.connect(func1);
    31. s.connect(func2);
    32. std::cout << s() << std::endl;
    33. }
    • 下载源代码

    合成器是一个重载了 operator()() 操作符的类。这个操作符会被自动调用,传入两个迭代器,指向某个特定信号的所有返回值。 以上例子使用了标准 C++ 算法 std::min_element() 来确定并返回最小的值。

    不幸的是,我们不可能把象 std::min_element() 这样的一个算法直接传给 boost::signal 作为一个模板参数。 boost::signal 要求这个合成器定义一个名为 result_type 的类型,用于说明 operator()() 操作符返回值的类型。 由于在标准 C++ 算法中缺少这个类型,所以在编译时会产生一个相应的错误。

    除了对返回值进行分析以外,合成器也可以保存它们。

    1. #include <boost/signal.hpp>
    2. #include <iostream>
    3. #include <vector>
    4. #include <algorithm>
    5.  
    6. int func1()
    7. {
    8. return 1;
    9. }
    10.  
    11. int func2()
    12. {
    13. return 2;
    14. }
    15.  
    16. template <typename T>
    17. struct min_element
    18. {
    19. typedef T result_type;
    20.  
    21. template <typename InputIterator>
    22. T operator()(InputIterator first, InputIterator last) const
    23. {
    24. return T(first, last);
    25. }
    26. };
    27.  
    28. int main()
    29. {
    30. boost::signal<int (), min_element<std::vector<int> > > s;
    31. s.connect(func1);
    32. s.connect(func2);
    33. std::vector<int> v = s();
    34. std::cout << *std::min_element(v.begin(), v.end()) << std::endl;
    35. }
    • 下载源代码

    这个例子把所有返回值保存在一个 vector 中,再由 s() 返回。