雖然這個庫的名字乍一看好象有點誤導,但實際上并非如此。 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();
}
- 下載下傳源代碼