天天看点

boost之路四 事件处理



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

因此,Boost.Signals 没有提供任何类似于 '事件' 的类。 相反,它提供了一个名为 ​

​boost::signal​

​​ 的类,定义于 ​

​boost/signal.hpp​

​. 实际上,这个头文件是唯一一个需要知道的,因为它会自动包含其它相关的头文件。

Boost.Signals 定义了其它一些类,位于 boost::signals 名字空间中。 由于 ​

​boost::signal​

​ 是最常被用到的类,所以它是位于名字空间 boost

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

void func() 
{ 
  std::cout << "Hello, world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  s.connect(func); 
  s(); 
}      
  • ​​下载源代码​​

​boost::signal​

​​ 实际上被实现为一个模板函数,具有被用作为事件处理器的函数的签名,该签名也是它的模板参数。 在这个例子中,只有签名为 ​

​void ()​

​ 的函数可以被成功关联至信号 s。

函数 ​

​func()​

​​ 被通过 ​

​connect()​

​​ 方法关联至信号 s。 由于 ​

​func()​

​​ 符合所要求的 ​

​void ()​

​​ 签名,所以该关联成功建立。因此当信号 s 被触发时,​

​func()​

​ 将被调用。

信号是通过调用 s 来触发的,就象普通的函数调用那样。 这个函数的签名对应于作为模板参数传入的签名:因为 ​

​ void ()​

​ 不要求任何参数,所以括号内是空的。

调用 s 会引发一个触发器,进而执行相应的 ​

​func()​

​​ 函数 - 之前用 ​

​connect()​

​ 关联了的。

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

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

void func() 
{ 
  std::cout << "Hello, world!" << std::endl; 
} 

int main() 
{ 
  boost::function<void ()> f; 
  f = func; 
  f(); 
}      
  • ​​下载源代码​​

和前一个例子相类似,​

​func()​

​​ 被关联至 f。 当 f 被调用时,就会相应地执行 ​

​func()​

​。 Boost.Function 仅限于这种情形下适用,而 Boost.Signals 则提供了多得多的方式,如关联多个函数至单个特定信号,示例如下。

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

void func1() 
{ 
  std::cout << "Hello" << std::flush; 
} 

void func2() 
{ 
  std::cout << ", world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  s.connect(func1); 
  s.connect(func2); 
  s(); 
}      
  • ​​下载源代码​​

​boost::signal​

​​ 可以通过反复调用 ​

​ connect()​

​​ 方法来把多个函数赋值给单个特定信号。 当该信号被触发时,这些函数被按照之前用 ​

​ connect()​

​ 进行关联时的顺序来执行。

另外,执行的顺序也可通过 ​

​connect()​

​​ 方法的另一个重载版本来明确指定,该重载版本要求以一个 ​

​int​

​ 类型的值作为额外的参数。

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

void func1() 
{ 
  std::cout << "Hello" << std::flush; 
} 

void func2() 
{ 
  std::cout << ", world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  s.connect(1, func2); 
  s.connect(0, func1); 
  s(); 
}      
  • ​​下载源代码​​

和前一个例子一样,​

​func1()​

​​ 在 ​

​func2()​

​ 之前执行。

要释放某个函数与给定信号的关联,可以用 ​

​disconnect()​

​ 方法。

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

void func1() 
{ 
  std::cout << "Hello" << std::endl; 
} 

void func2() 
{ 
  std::cout << ", world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  s.connect(func1); 
  s.connect(func2); 
  s.disconnect(func2); 
  s(); 
}      
  • ​​下载源代码​​

这个例子仅输出 ​

​Hello​

​​,因为与 ​

​func2()​

​ 的关联在触发信号之前已经被释放。

除了 ​

​connect()​

​​ 和 ​

​disconnect()​

​​ 以外,​

​boost::signal​

​ 还提供了几个方法。

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

void func1() 
{ 
  std::cout << "Hello" << std::flush; 
} 

void func2() 
{ 
  std::cout << ", world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  s.connect(func1); 
  s.connect(func2); 
  std::cout << s.num_slots() << std::endl; 
  if (!s.empty()) 
    s(); 
  s.disconnect_all_slots(); 
}      
  • ​​下载源代码​​

​num_slots()​

​​ 返回已关联函数的数量。如果没有函数被关联,则 ​

​ num_slots()​

​​ 返回0。 在这种特定情况下,可以用 ​

​empty()​

​​ 方法来替代。 ​

​disconnect_all_slots()​

​ 方法所做的实际上正是它的名字所表达的:释放所有已有的关联。

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

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

int func1() 
{ 
  return 1; 
} 

int func2() 
{ 
  return 2; 
} 

int main() 
{ 
  boost::signal<int ()> s; 
  s.connect(func1); 
  s.connect(func2); 
  std::cout << s() << std::endl; 
}      
  • ​​下载源代码​​

​func1()​

​​ 和 ​

​func2()​

​​ 都具有 ​

​int​

​ 类型的返回值。 s

以上例子实际上会把 ​

​2​

​ 写出至标准输出流。 两个返回值都被 s

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

​ boost::signal​

​。

#include <boost/signal.hpp> 
#include <iostream> 
#include <algorithm> 

int func1() 
{ 
  return 1; 
} 

int func2() 
{ 
  return 2; 
} 

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

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

int main() 
{ 
  boost::signal<int (), min_element<int> > s; 
  s.connect(func1); 
  s.connect(func2); 
  std::cout << s() << std::endl; 
}      
  • ​​下载源代码​​

合成器是一个重载了 ​

​operator()()​

​​ 操作符的类。这个操作符会被自动调用,传入两个迭代器,指向某个特定信号的所有返回值。 以上例子使用了标准 C++ 算法 ​

​std::min_element()​

​ 来确定并返回最小的值。

不幸的是,我们不可能把象 ​

​std::min_element()​

​​ 这样的一个算法直接传给 ​

​ boost::signal​

​​ 作为一个模板参数。 ​

​boost::signal​

​​ 要求这个合成器定义一个名为 ​

​result_type​

​​ 的类型,用于说明 ​

​operator()()​

​ 操作符返回值的类型。 由于在标准 C++ 算法中缺少这个类型,所以在编译时会产生一个相应的错误。

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

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

int func1() 
{ 
  return 1; 
} 

int func2() 
{ 
  return 2; 
} 

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

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

int main() 
{ 
  boost::signal<int (), min_element<std::vector<int> > > s; 
  s.connect(func1); 
  s.connect(func2); 
  std::vector<int> v = s(); 
  std::cout << *std::min_element(v.begin(), v.end()) << std::endl; 
}      
  • ​​下载源代码​​

这个例子把所有返回值保存在一个 vector 中,再由 ​

​s()​

​ 返回。

4.3. 连接 Connections

函数可以通过由 ​

​boost::signal​

​​ 所提供的 ​

​ connect()​

​​ 和 ​

​disconnect()​

​​ 方法的帮助来进行管理。 由于 ​

​ connect()​

​​ 会返回一个类型为 ​

​boost::signals::connection​

​ 的值,它们可以通过其它方法来管理。

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

void func() 
{ 
  std::cout << "Hello, world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  boost::signals::connection c = s.connect(func); 
  s(); 
  c.disconnect(); 
}      
  • ​​下载源代码​​

​boost::signal​

​​ 的 ​

​disconnect()​

​​ 方法需要传入一个函数指针,而直接调用 ​

​boost::signals::connection​

​​ 对象上的 ​

​ disconnect()​

​ 方法则略去该参数。

除了 ​

​disconnect()​

​​ 方法之外,​

​boost::signals::connection​

​​ 还提供了其它方法,如 ​

​block()​

​​ 和 ​

​unblock()​

​。

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

void func() 
{ 
  std::cout << "Hello, world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  boost::signals::connection c = s.connect(func); 
  c.block(); 
  s(); 
  c.unblock(); 
  s(); 
}      
  • ​​下载源代码​​

以上程序只会执行一次 ​

​func()​

​​。 虽然信号 s 被触发了两次,但是在第一次触发时 ​

​func()​

​​ 不会被调用,因为连接 c 实际上已经被 ​

​ block()​

​​ 调用所阻塞。 由于在第二次触发之前调用了 ​

​unblock()​

​​,所以之后 ​

​func()​

​ 被正确地执行。

除了 ​

​boost::signals::connection​

​​ 以外,还有一个名为 ​

​ boost::signals::scoped_connection​

​ 的类,它会在析构时自动释放连接。

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

void func() 
{ 
  std::cout << "Hello, world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  { 
    boost::signals::scoped_connection c = s.connect(func); 
  } 
  s(); 
}      
  • ​​下载源代码​​

因为连接对象 c 在信号触发之前被销毁,所以 ​

​func()​

​ 不会被调用。

​boost::signals::scoped_connection​

​​ 实际上是派生自 ​

​ boost::signals::connection​

​​ 的,所以它提供了相同的方法。它们之间的区别仅在于,在析构 ​

​ boost::signals::scoped_connection​

​ 时,连接会自动释放。

虽然 ​

​boost::signals::scoped_connection​

​ 的确令自动释放连接更为容易,但是该类型的对象仍需要管理。 如果在其它情形下连接也可以被自动释放,而且不需要管理这些对象的话,就更好了。

#include <boost/signal.hpp> 
#include <boost/bind.hpp> 
#include <iostream> 
#include <memory> 

class world 
{ 
  public: 
    void hello() const 
    { 
      std::cout << "Hello, world!" << std::endl; 
    } 
}; 

int main() 
{ 
  boost::signal<void ()> s; 
  { 
    std::auto_ptr<world> w(new world()); 
    s.connect(boost::bind(&world::hello, w.get())); 
  } 
  std::cout << s.num_slots() << std::endl; 
  s(); 
}      
  • ​​下载源代码​​

以上程序使用 Boost.Bind 将一个对象的方法关联至一个信号。 在信号触发之前,这个对象就被销毁了,这会产生问题。 我们不传递实际的对象 w,而只传递一个指针给 ​

​boost::bind()​

​​。 在 ​

​s()​

​ 被实际调用的时候,该指针所引向的对象已不再存在。

可以如下修改这个程序,使得一旦对象 w

#include <boost/signal.hpp> 
#include <boost/bind.hpp> 
#include <iostream> 
#include <memory> 

class world : 
  public boost::signals::trackable 
{ 
  public: 
    void hello() const 
    { 
      std::cout << "Hello, world!" << std::endl; 
    } 
}; 

int main() 
{ 
  boost::signal<void ()> s; 
  { 
    std::auto_ptr<world> w(new world()); 
    s.connect(boost::bind(&world::hello, w.get())); 
  } 
  std::cout << s.num_slots() << std::endl; 
  s(); 
}      
  • ​​下载源代码​​