天天看點

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(); 
}      
  • ​​下載下傳源代碼​​