Boost教程之事件处理

很多开发者在听到术语'事件处理'时就会想到GUI:点击一下某个按钮,相关联的功能就会被执行。 点击本身就是事件,而功能就是相对应的事件处理器。这一模式的使用当然不仅限于GUI。 一般情况下,任意对象都可以调用基于特定事件的专门函数。

1、 信号 Signals

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

#include  
#include  

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

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

int main() 
{ 
  boost::signal s; 
  s.connect(func1); 
  s.connect(func2); 
  s(); 
} 

boost::signal 实际上被实现为一个模板函数,具有被用作为事件处理器的函数的签名,该签名也是它的模板参数。 在这个例子中,只有签名为 void () 的函数可以被成功关联至信号 s。函数 func1() 及func2()被通过 connect() 方法关联至信号 s。 由于 func1() 及func2()符合所要求的 void () 签名,所以该关联成功建立。因此当信号 s 被触发时,func1()及func2()会按照connect的顺序被调用。

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

#include  
#include  

int func1() 
{ 
  std::cout << "Hello" << std::flush; 
  return 1;
} 

int func2() 
{ 
  std::cout << ", world!" << std::endl; 
  return 2;
} 

int main() 
{ 
  boost::signal s; 
  s.connect(1, func2); 
  s.connect(0, func1); 
  s(); 
} 

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

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

num_slots() 返回已关联函数的数量。如果没有函数被关联,则 num_slots() 返回0。

disconnect_all_slots() 方法释放所有已有的关联。

empty()方法判断当前信号是否已关联函数。

另外,关于函数返回值,缺省情况下,所有被关联函数中,实际上只有最后一个调用的函数的返回值被真正返回。

若想对所有返回值进行操作处理,可以把一个称为合成器(combiner)的东西作为第二个参数传递给 boost::signal。例如:

#include  
#include  
#include  
#include  

int func1() 
{ 
  return 1; 
} 

int func2() 
{ 
  return 2; 
} 

template  
struct min_element 
{ 
  typedef T result_type; 

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

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

合成器其实就是一个重载了 operator()() 操作符的类。这个操作符会被自动调用,传入两个迭代器,指向某个特定信号的所有返回值。另外这段代码在实际编译是,是有警告的,因为boost::signal 要求合成器的operator()返回名为 result_type 的类型。 而在标准 C++ 算法中缺少这个类型。

2、连接 Connections

实际上 boost::signal变量在调用connect()disconnect() 方法时会返回一个类型为 boost::signals::connection 的值。而我们则可以通过该返回值对boost::signal进行管理。例如:

#include  
#include  

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

int main() 
{ 
  boost::signal s; 
  boost::signals::connection c = s.connect(func); 
  c.block(); 
  s(); 
  c.unblock(); 
  s(); 
  c.disconnect();  
} 

如上代码所示:boost::signal 的 disconnect() 方法需要传入一个函数指针,而直接调用 boost::signals::connection 对象上的 disconnect() 方法则略去该参数。另外以上程序只会执行一次 func()。 虽然信号 s 被触发了两次,但是在第一次触发时 func() 不会被调用,因为连接 c 实际上已经被 block() 调用所阻塞。 由于在第二次触发之前调用了 unblock(),所以之后 func() 被正确地执行。

除了 boost::signals::connection 以外,还有一个名为 boost::signals::scoped_connection 的类,它会在析构时自动释放连接。

最后,信号也是可以关联一个类的成员函数的,例如:

#include  
#include  
#include  
#include  

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

int main() 
{ 
  boost::signal s; 
  { 
    std::auto_ptr 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  
#include  
#include  
#include  

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

int main() 
{ 
  boost::signal s; 
  { 
    std::auto_ptr w(new world()); 
    s.connect(boost::bind(&world::hello, w.get())); 
  } 
  std::cout << s.num_slots() << std::endl; 
  s(); 
} 

 仅需的修改是让 world 类继承自 boost::signals::trackable。 当使用对象的指针而不是对象的副本来关联函数至信号时,boost::signals::trackable 可以显著简化连接的管理。

你可能感兴趣的:(Boost教程)