boost源码剖析之:多重回调机制signal(上)
boost源碼剖析之:多重回調(diào)機(jī)制signal(上)
?
劉未鵬
C++的羅浮宮(http://blog.csdn.net/pongba)
?
boost庫(kù)固然是技術(shù)的寶庫(kù),卻更是思想的寶庫(kù)。大多數(shù)程序員都知道如何應(yīng)用command,observer等模式,卻不知該如何寫一個(gè)支持該模式的類。正如隔靴搔癢,無(wú)法深入。DDJ上曾有一篇文章用C++實(shí)現(xiàn)類似C#的event機(jī)制,不過(guò)是個(gè)雛形,比之boost.Signal卻又差之甚遠(yuǎn)矣。
?
上篇:架構(gòu)篇
引入
所謂“事件”機(jī)制,簡(jiǎn)而言之,就是用戶將自己的一個(gè)或多個(gè)回調(diào)函數(shù)掛鉤到某個(gè)“事件”上,一旦“事件”被觸發(fā),所有掛鉤的函數(shù)都被調(diào)用。
?
毫無(wú)疑問(wèn),事件機(jī)制是個(gè)十分有用且常用的機(jī)制,不然C#也不會(huì)將它在語(yǔ)言層面實(shí)現(xiàn)了。
?
但是C++語(yǔ)言并無(wú)此種機(jī)制。
?
幸運(yùn)的是boost庫(kù)的開(kāi)發(fā)者們替我們做好了這件事(事實(shí)上,他們做的還要更多些)。他們的類稱作signal,即“信號(hào)”的意思,當(dāng)“信號(hào)”發(fā)出的時(shí)候,所有注冊(cè)過(guò)的函數(shù)都將受到調(diào)用。這與“事件”本質(zhì)上完全一樣。
?
簡(jiǎn)單情況下,你只需要這樣寫:
?
double?square(double?d){return?pi*r*r;}?//面積
double?circle(double?d){return?2*pi*r;}?//周長(zhǎng)
//double(double)是一個(gè)函數(shù)類型,意即:接受一個(gè)double型參數(shù),返回double。
signal<double(double)[1]> sig;
sig.connect(&square);?//向sig注冊(cè)square
sig.connect(&circle);//注冊(cè)circle
//觸發(fā)該信號(hào),sig會(huì)自動(dòng)調(diào)用square(3.14),circle(3.14),并返回最后一個(gè)函數(shù),circle()的返回值
double?c=sig(3.14);??//assert(c==circle(3.14))
?
signal能夠維護(hù)一系列的回調(diào)函數(shù),并且,signal還允許用戶指定函數(shù)的調(diào)用順序,signal還允許用戶定制其返回策略,默認(rèn)情況下返回(與它掛鉤的)最后一個(gè)函數(shù)的返回值,當(dāng)然你可以指定你自己的“返回策略”(比如:返回其中的最大值),其中手法,甚為精巧。另外,如果注冊(cè)的是函數(shù)對(duì)象(仿函數(shù))而非普通函數(shù),則signal還提供了跟蹤能力,即該函數(shù)對(duì)象一旦析構(gòu),則連接自動(dòng)斷開(kāi),其實(shí)現(xiàn)更是精妙無(wú)比。
?
俗語(yǔ)云:“熟讀唐詩(shī)三百首,不會(huì)吟詩(shī)也會(huì)吟”。寫程序更是如此。如果仔細(xì)體會(huì),會(huì)發(fā)現(xiàn)signal的實(shí)現(xiàn)里面隱藏了許許多多有價(jià)值的思想和模式。何況boost庫(kù)是個(gè)集泛型技術(shù)之大成的庫(kù),其源代碼本身就是一筆財(cái)富,對(duì)于深入學(xué)習(xí)C++泛型技術(shù)是極好的教材。所以本文不講應(yīng)用,只講實(shí)現(xiàn),你可以邊讀邊參照boost庫(kù)的源代碼[2]。另外,本文盡量少羅列代碼,多分析架構(gòu)和思想,并且列出的代碼為了簡(jiǎn)潔起見(jiàn),往往稍作簡(jiǎn)化[3],略去了一些細(xì)節(jié),但是都注明其源文件,自行參照。
?
在繼續(xù)往下讀之前,建議大家先看看boost庫(kù)的官方文檔,了解signal的各種使用情況,這樣,在經(jīng)歷下面繁復(fù)的分析過(guò)程時(shí)心中才會(huì)始終有一個(gè)清晰的脈絡(luò)。事實(shí)上,我在閱讀代碼之前也是從各種例子入手的。
?
架構(gòu)
Signal的內(nèi)部架構(gòu),如果給出它的總體輪廓,非常清晰明了。見(jiàn)下圖:
?
圖一
???????????????
?
顯然,signal在內(nèi)部需要一個(gè)管理設(shè)施來(lái)管理用戶所注冊(cè)的函數(shù)(這就是圖中的slot manager),從根本上來(lái)說(shuō),boost::signal中的這個(gè)slot“管理器”就是multimap(如果你不熟悉multimap,可以參考一些STL方面的書(shū)籍(如《C++ STL》《泛型編程與STL》)或干脆查詢MSDN。這里我只簡(jiǎn)單的說(shuō)一下——multimap將鍵(key)映射(map)到鍵值(鍵和鍵值的類型可以是任意),就像字典將字母映射到頁(yè)碼一樣。)它負(fù)責(zé)保存所謂的slot,每一個(gè)slot其實(shí)本質(zhì)上是一個(gè)boost::function[4]函數(shù)對(duì)象,該函數(shù)對(duì)象封裝了用戶注冊(cè)給signal回調(diào)的函數(shù)(或仿函數(shù))。當(dāng)然,slot是經(jīng)過(guò)某種規(guī)則排序的。這正是signal能夠控制函數(shù)調(diào)用順序的原因。
?
當(dāng)你觸發(fā)signal時(shí),其內(nèi)部迭代遍歷“管理器”——multimap,找出其中保存的所有函數(shù)或函數(shù)對(duì)象并逐一調(diào)用它們。
?
聽(tīng)起來(lái)很簡(jiǎn)單,是不是?但是我其實(shí)略去了若干細(xì)節(jié),譬如,如何讓用戶控制某個(gè)特定的連接?如何控制函數(shù)的調(diào)用順序?如何實(shí)現(xiàn)可定制的返回策略?等等。
?
看來(lái)設(shè)計(jì)一個(gè)“industry-strength”的signal并非一件易事。事實(shí)上,非常不易。然而,雖然我們做不到,卻可以看看大師們的手筆。
?
我們從signal的最底層布局開(kāi)始,signal的底層布局十分簡(jiǎn)單,由一個(gè)基類signal_base_impl來(lái)實(shí)現(xiàn)。下面就是該基類的代碼:
?
摘自boost/signals/detail/signal_base.hpp
class?signal_base_impl {
public:
typedef?function2<bool, any, any> compare_type;
private:
typedef?std::multimap<any,?connection_slot_pair, compare_type>slot_container_type;?//以multimap作為slot管理器的類型
?
????//遍歷slot容器的迭代器類型
typedef?slot_container_type::iterator slot_iterator;
?????//slot容器內(nèi)部元素的類型,事實(shí)上,那其實(shí)就是std::pair<any,connection_slot_pair>。
????typedef?slot_container_type::value_type stored_slot_type;
?
?????//這就是slot管理器,唯一的數(shù)據(jù)成員——一個(gè)multimap,負(fù)責(zé)保存所有的slot。
????mutable?slot_container_type?slots_;
...
};
?
可以看出slot管理器的類型是個(gè)multimap,其鍵(key)類型卻是any[5],這是個(gè)泛型的指針,可以指向任何對(duì)象,為什么不是整型或其它類型,后面會(huì)為你解釋。
以上是主要部分,你可能會(huì)覺(jué)得奇怪,為什么保存在slot管理器內(nèi)部的元素類型是個(gè)怪異的connection_slot_pair而不是boost::function,前面不是說(shuō)過(guò),slot本質(zhì)上就是boost::function對(duì)象么?要尋求答案,最好的辦法就是看看這個(gè)類型定義的代碼,源代碼會(huì)交代一切。下面就是connection_slot_pair的定義:
?
摘自boost/signals/connection.hpp
struct?connection_slot_pair {
//connection類用來(lái)表現(xiàn)“連接”這個(gè)概念,用戶通過(guò)connection對(duì)象來(lái)控制相應(yīng)的連接,例如,調(diào)用成員函數(shù)disconnect()則斷開(kāi)該連接
connection?first;
//any是個(gè)泛型指針類,可以指向任何類型的對(duì)象
????any?second;
//封裝用戶注冊(cè)的函數(shù)的boost::function對(duì)象實(shí)際上就由這個(gè)泛型指針來(lái)持有
...
};
?
原來(lái),slot管理器內(nèi)部的確保存著boost::function對(duì)象,只不過(guò)由connection_slot_pair里的second成員——一個(gè)泛型指針any——來(lái)持有。并且,還多出了一個(gè)額外的connection對(duì)象——很顯然,它們是有關(guān)聯(lián)的——connection成員表現(xiàn)的正是該function與signal的連接。為什么要多出這么一個(gè)成員呢?原因是這樣的:connection一般掌握在用戶手中,代碼象這樣:
?
connection con=sig.connect(&f); //?通過(guò)con來(lái)控制這個(gè)連接
?
而signal如果在該連接還沒(méi)有被用戶斷開(kāi)(即用戶還沒(méi)有調(diào)用con.disconnect())前就析構(gòu)了,自然要將其中保存的所有slot一一摧毀,這時(shí)候,如果slot管理器內(nèi)部沒(méi)有保存connection的副本,則slot管理器就無(wú)法對(duì)每個(gè)slot一一斷開(kāi)其相應(yīng)的連接,從而控制在用戶手中的connection對(duì)象就仿佛一個(gè)成了一個(gè)野指針,這是件很危險(xiǎn)的事情。從另一個(gè)方面說(shuō),既然slot管理器內(nèi)部保存了connection的副本,則只要讓這些connection對(duì)象析構(gòu)的時(shí)候能自動(dòng)斷開(kāi)連接就行了,這樣,即使用戶后來(lái)還試圖斷開(kāi)手里的con連接,也能夠得知該連接已經(jīng)斷開(kāi)了,不會(huì)出現(xiàn)危險(xiǎn)。有關(guān)connection的詳細(xì)分析見(jiàn)下文。
?
根據(jù)目前的分析,signal的架構(gòu)可以這樣表示:
?
圖二
????
?
boost::signals::connection類
connection類是為了表現(xiàn)signal與具體的slot之間的“連接”這種概念。signal將slot安插妥當(dāng)后會(huì)返回一個(gè)connection對(duì)象,用戶可以持有這個(gè)對(duì)象并以此操縱與它對(duì)應(yīng)的“連接”。而每個(gè)slot自己也和與它對(duì)應(yīng)的connection呆在一起(見(jiàn)上圖),這樣slot管理器就能夠經(jīng)由connection_slot_pair中的first元素來(lái)管理“連接”,也就是說(shuō),當(dāng)signal析構(gòu)時(shí),需要斷開(kāi)與它連接的所有slot,這時(shí)就利用connection_slot_pair中的first成員來(lái)斷開(kāi)連接。而從實(shí)際上來(lái)說(shuō),slot管理器在析構(gòu)時(shí)卻又不用作任何額外的工作,只需按部就班的析構(gòu)它的所有成員(slot)就行了,因?yàn)?/span>connection對(duì)象在析構(gòu)時(shí)會(huì)考慮自動(dòng)斷開(kāi)連接(當(dāng)其內(nèi)部的is_controlling標(biāo)志為true時(shí))。
?
要注意的是,對(duì)于同一個(gè)連接可能同時(shí)存在多個(gè)connection對(duì)象來(lái)表現(xiàn)(和控制)它,但始終有一個(gè)connection對(duì)象是和slot呆在一起的,以保證在signal析構(gòu)時(shí)能夠斷開(kāi)相應(yīng)的連接,其它連接則掌握在用戶手中,并且允許拷貝。很顯然,一旦實(shí)際的連接被某個(gè)connection斷開(kāi),則對(duì)應(yīng)于該連接的其它connection對(duì)象應(yīng)該全部失效,但是庫(kù)的設(shè)計(jì)者并不知道用戶什么時(shí)候會(huì)拷貝connection對(duì)象和持有多少個(gè)connection對(duì)象,那么用戶經(jīng)過(guò)其中一個(gè)connection對(duì)象斷開(kāi)連接時(shí),其它connection對(duì)象又是如何知道它們對(duì)應(yīng)的連接是否已經(jīng)斷開(kāi)呢?原因是這樣的:對(duì)于某個(gè)特定連接,真正表現(xiàn)該連接的只有唯一的一個(gè)basic_connection對(duì)象。而connection對(duì)象其實(shí)只是個(gè)外包類,其中有一個(gè)成員是個(gè)shared_ptr[6]類型的智能指針,從而對(duì)應(yīng)于同一個(gè)連接的所有connection對(duì)象其實(shí)都通過(guò)這個(gè)智能指針指向同一個(gè)basic_connection對(duì)象,后者唯一表現(xiàn)了這個(gè)連接。經(jīng)過(guò)再次精化后的架構(gòu)圖如下:
?
圖三
?
這樣,當(dāng)用戶通過(guò)其中任意一個(gè)connection對(duì)象斷開(kāi)連接(或signal通過(guò)與slot保存在一塊的connection對(duì)象斷開(kāi)連接)時(shí),connection對(duì)象只需轉(zhuǎn)交具體表現(xiàn)該連接的唯一的basic_connection對(duì)象,由它來(lái)真正斷開(kāi)連接即可。這里,需要注意的是,斷開(kāi)連接并非意味著唯一表示該連接的basic_connection對(duì)象的析構(gòu)。前面已經(jīng)講過(guò),connection類里有一個(gè)shared_ptr智能指針指向basic_connection對(duì)象,所以,當(dāng)指向basic_connection的所有connection都析構(gòu)掉后,智能指針自然會(huì)將basic_connection析構(gòu)。其實(shí)更重要的原因是,從邏輯上,basic_connection還充當(dāng)了信息中介——由于控制同一連接的所有connection對(duì)象都共享它,從而都可以查看它的狀態(tài)來(lái)得知連接是否已經(jīng)斷開(kāi),如果將它delete掉了,則其它connection就無(wú)從得知連接的狀態(tài)了。所以這種設(shè)計(jì)是有良苦用心的。正因此,一旦某個(gè)連接被斷開(kāi),則對(duì)應(yīng)于它的所有connection對(duì)象都可得知該連接已經(jīng)斷開(kāi)了。
?
對(duì)于connection,還有一個(gè)特別的規(guī)則:connection對(duì)象分為兩種,一種是“控制性”的,另一種是“非控制性”的。掌握在用戶手中的connection對(duì)象為“非控制性”的,也就是說(shuō)析構(gòu)時(shí)不會(huì)導(dǎo)致連接的斷開(kāi)——這符合邏輯,因?yàn)橛脩羰种械?/span>connection對(duì)象通常只是暫時(shí)的復(fù)制品,很快就會(huì)因?yàn)榻Y(jié)束生命期而被析構(gòu)掉,況且,signal::connect()返回的connection對(duì)象也是臨時(shí)對(duì)象,用戶可以選擇丟棄該返回值(即不用手動(dòng)管理該連接),此時(shí)該返回值會(huì)立即析構(gòu),這當(dāng)然不應(yīng)該導(dǎo)致連接的斷開(kāi),所以這種connection對(duì)象是“非控制性”的。而保存在slot管理器內(nèi)部,與相應(yīng)的slot呆在一起的connection對(duì)象則是“控制性”的,一旦析構(gòu),則會(huì)斷開(kāi)連接——這是因?yàn)樗奈鰳?gòu)通常是由signal對(duì)象的析構(gòu)導(dǎo)致的,所謂“樹(shù)倒猢猻散”,signal都不存在了,當(dāng)然要斷開(kāi)所有與它相關(guān)的連接了。
?
了解了這種架構(gòu),我們?cè)賮?lái)跟蹤一下具體的連接過(guò)程。
?
連接
向signal注冊(cè)一個(gè)函數(shù)(或仿函數(shù))甚為簡(jiǎn)單,只需調(diào)用signal::connect()并將該函數(shù)(或仿函數(shù))作為參數(shù)傳遞即可。不過(guò),要注意的是,注冊(cè)普通函數(shù)時(shí)需提供函數(shù)的地址才行(即“&f”),而注冊(cè)函數(shù)對(duì)象時(shí)只需將對(duì)象本身作為參數(shù)。下面,我們從signal::connect()開(kāi)始來(lái)跟蹤signal的連接過(guò)程。
?
前提:下面跟蹤的全過(guò)程都假設(shè)用戶注冊(cè)的是普通函數(shù),這樣有助于先理清脈絡(luò),至于注冊(cè)仿函數(shù)(即函數(shù)對(duì)象)時(shí)情況如何,將在高級(jí)篇中分析。
?
源代碼能夠說(shuō)明一切,下面就是signal::connect()的代碼:
?
?????template<...>
?????connection signal<...>::connect(const?slot_type& in_slot)
?????{...}
?
這里,我們先不管connect()函數(shù)內(nèi)部是如何運(yùn)作的,而是集中于它的唯一一個(gè)參數(shù),其類型卻是const slot_type&,這個(gè)類型其實(shí)對(duì)用戶提供的函數(shù)(或仿函數(shù))進(jìn)行一重封裝——封裝為一個(gè)“slot”。至于為什么要多出這么一個(gè)中間層,原因只是想提供給用戶一個(gè)額外的自由度,具體細(xì)節(jié)容后再述。
?
slot_type其實(shí)只是一個(gè)位于signal類內(nèi)部的typedef,其真實(shí)類型為slot類。
?
很顯然,這里,slot_type的構(gòu)造函數(shù)將被調(diào)用(參數(shù)是用戶提供的函數(shù)或仿函數(shù))以創(chuàng)建一個(gè)臨時(shí)對(duì)象,并將它綁定到這個(gè)const引用。下面就是它的構(gòu)造函數(shù):
?
?????template<typename?F>
????slot(const?F& f) : slot_function(f)
????{
??????...?//這里,我們先略過(guò)該構(gòu)造函數(shù)里面的代碼(后面再回顧)
}
?
可以看出,用戶給出的函數(shù)(或仿函數(shù))被封裝在slot_function成員中,slot_function的類型其實(shí)是boost::function<...>,這是個(gè)泛型的函數(shù)指針,封裝任何簽名兼容的函數(shù)及仿函數(shù)。將來(lái)保存在slot管理器內(nèi)部的就是它。
?
下面,slot臨時(shí)對(duì)象構(gòu)造完畢,仍然回到signal::connect()來(lái):
?
摘自boost/signals/signal_template.hpp
connection signal<...>::connect(const?slot_type& in_slot)
{
?????...
?????????return?impl->connect_slot(in_slot.get_slot_function(),
??????????????????????????????any(),
??????????????????????????????in_slot.get_bound_objects());
}
?
這里,signal將一切又交托給了其基類的connect_slot()函數(shù),并提供給它三個(gè)參數(shù),注意,第一個(gè)參數(shù)in_slot.get_slot_function()返回的其實(shí)正是剛才所說(shuō)的slot類的成員slot_function,也正是將要保存在slot管理器內(nèi)部的boost::function對(duì)象。而第二個(gè)參數(shù)表示該用戶注冊(cè)函數(shù)的優(yōu)先級(jí),
?
signal::connect()其實(shí)有兩個(gè)重載版本,第一個(gè)只有一個(gè)參數(shù),就是用戶提供的函數(shù),第二個(gè)卻有兩個(gè)參數(shù),其第一個(gè)參數(shù)為優(yōu)先級(jí),默認(rèn)是一個(gè)整數(shù)。這里,我們考察的是只有一個(gè)參數(shù)的版本,意味著用戶不關(guān)心該函數(shù)的優(yōu)先級(jí),所以默認(rèn)構(gòu)造一個(gè)空的any()對(duì)象(回憶一下,slot管理器的鍵(key)類型為any)。至于第三個(gè)參數(shù)僅在用戶注冊(cè)函數(shù)對(duì)象時(shí)有用,我們暫時(shí)略過(guò),在高級(jí)篇里再詳細(xì)敘述。現(xiàn)在,繼續(xù)追蹤至connect_slot()的定義:
?
摘自libs/signals/src/signal_base.cpp
connection
??????signal_base_impl::
????????connect_slot(const?any& slot,
?????????????????????const?any& name,
?????????????????????const?std::vector<const trackable*>& bound_objects)
//最后一個(gè)參數(shù)當(dāng)用戶提供仿函數(shù)時(shí)方才有效,容后再述
{
?????//創(chuàng)建一個(gè)basic_connection以表現(xiàn)本連接——注意,一個(gè)連接只對(duì)應(yīng)于一個(gè)basic_connection對(duì)象,但可以有多個(gè)connection對(duì)象來(lái)操縱它。具體原因上文有詳述。
????????basic_connection* con =?new?basic_connection();
????????connection slot_connection;
????????slot_connection.reset(con);
?
????????std::auto_ptr<slot_iterator> saved_iter(new?slot_iterator());
?
?????//用戶注冊(cè)的函數(shù)在此才算真正在signal內(nèi)部安家落戶——即將它插入到slot管理器(multimap)中去
????slot_iterator pos =
????????????????slots_.insert(stored_slot_type(name,
connection_slot_pair(slot_connection,slot)
));
//保存在slot管理器內(nèi)部的connection對(duì)象應(yīng)該設(shè)為“控制性”的。具體原因上文有詳述。
????????pos->second.first.set_controlling();
????????*saved_iter = pos;
//下面設(shè)置表現(xiàn)本連接的basic_connection對(duì)象的各項(xiàng)數(shù)據(jù),以便管理該連接。
????????con->signal?=?this;?//指向連接到的signal
????????con->signal_data?= saved_iter.release();//一個(gè)iterator,指出回調(diào)函數(shù)在signal中的slot管理器中的位置
????????con->signal_disconnect?= &signal_base_impl::slot_disconnected;?//如果想斷開(kāi)連接,則應(yīng)該調(diào)用此函數(shù),并將前面兩項(xiàng)數(shù)據(jù)作為參數(shù)傳遞過(guò)去,則回調(diào)函數(shù)將被從slot管理器中移除。
????????????...
????????return?slot_connection;//返回該連接
}
?
這個(gè)函數(shù)結(jié)束后,連接也就創(chuàng)建完了,看一看最后一行代碼,正是返回該連接。
?
從上面的代碼可以看出,basic_connection對(duì)象有三個(gè)成員:signal,signal_data,signal_disconnect,這三個(gè)成員起到了控制該連接的作用。源代碼上的注釋已經(jīng)提到,成員signal指向連接到的是哪個(gè)signal。而signal_data其實(shí)是個(gè)iterator,指明了該slot在slot管理器中的位置。最后,成員signal_disconnect則是個(gè)void(*)(void*,void*)型的函數(shù)指針,指向一個(gè)static成員函數(shù)——signal_base_impl::slot_disconnected。以basic_connection中的signal和signal_data兩個(gè)成員作為參數(shù)來(lái)調(diào)用這個(gè)函數(shù)就能夠斷開(kāi)該連接。即:
?
(*signal_disconnect)(local_con->signal, local_con->signal_data);
?
然而,具體如何斷開(kāi)連接還得看slot_disconnected函數(shù)的代碼(注意將它和上面的connect_slot函數(shù)的代碼作一個(gè)比較,它們是幾乎相反的過(guò)程)
?
摘自libs/signals/src/signal_base.cpp
void?signal_base_impl::slot_disconnected(void* obj,?void* data)
{
????signal_base_impl* self =?reinterpret_cast<signal_base_impl*>(obj);//指明連接到的是哪個(gè)signal
?
?????//指出slot在slot管理器中的位置
????std::auto_ptr<slot_iterator> slot(
???????????????????????????reinterpret_cast<slot_iterator*>(data));
...?//省略部分代碼。
????self->slots_.erase(*slot);//將相應(yīng)的slot從slot管理器中移除
}
?
值得注意的是,basic_connection中的兩個(gè)成員:signal和signal_data的類型都是void*,具體原因在高級(jí)篇里會(huì)作解釋。而slot_disconnected函數(shù)的代碼不出所料:先將兩個(gè)參數(shù)的類型轉(zhuǎn)換為合適的類型,還其本來(lái)面目:一個(gè)是signal_base_impl*,另一個(gè)是指向迭代器的指針:slot_iterator*,然后調(diào)用slots_[7]上的erase函數(shù)將相應(yīng)的slot移除,就算完成了這次disconnect。這簡(jiǎn)直就是connect_slot()的逆過(guò)程。
?
這里,你可能會(huì)有疑問(wèn):這樣就算斷開(kāi)了連接?那么用戶如果不慎通過(guò)某個(gè)指向該basic_connection的connection再次試圖斷開(kāi)連接又當(dāng)如何呢?更可能的情況是,用戶想要再次查詢?cè)撨B接是否斷開(kāi)。如此說(shuō)來(lái),basic_connection中是否應(yīng)該有一個(gè)標(biāo)志,表示該連接是否已斷開(kāi)?完全不必,其第三個(gè)成員signal_disconnect是個(gè)函數(shù)指針,當(dāng)斷開(kāi)連接后,將它置為0,不就是個(gè)天然的標(biāo)志么?事實(shí)上,connection類的成員函數(shù)connected()就是這樣查詢連接狀態(tài)的:
?
摘自boost/signals/connection.hpp
bool?connected()?const
{
return?con.get() && con->signal_disconnect;
}
?
再次提醒一下,con是個(gè)shared_ptr,指向basic_connection對(duì)象。并且,尤其要注意的是,連接斷開(kāi)后,表示該連接的basic_connection對(duì)象并不析構(gòu),也不能析構(gòu),因?yàn)樗€要充當(dāng)連接狀態(tài)的標(biāo)志,以供仍可能在用戶手中的connection對(duì)象來(lái)查詢。當(dāng)指向它的所有connection對(duì)象都析構(gòu)時(shí),根據(jù)shared_ptr的規(guī)則,它自然會(huì)析構(gòu)掉。
?
好了,回到主線,連接和斷開(kāi)連接的大致過(guò)程都已經(jīng)分析完了。其中我略去了很多技術(shù)細(xì)節(jié),盡量使過(guò)程簡(jiǎn)潔,這些技術(shù)細(xì)節(jié)大多與仿函數(shù)有關(guān)——假若用戶注冊(cè)的是個(gè)仿函數(shù),就有得折騰了,其中曲折甚多,我會(huì)在高級(jí)篇里詳細(xì)分析。
?
排序
跟蹤完了連接過(guò)程,下面是真正的調(diào)用過(guò)程,即觸發(fā)了signal,各個(gè)注冊(cè)的函數(shù)均獲得一次調(diào)用,這個(gè)過(guò)程邏輯上頗為簡(jiǎn)單:從slot管理器中將它們一一取出并調(diào)用一次不就得了?但是,正如前面所說(shuō)的,調(diào)用可是要考慮順序的,各個(gè)函數(shù)可能有著不同的優(yōu)先級(jí),這又該如何管理呢?問(wèn)題的關(guān)鍵就在于multimap的排序,一旦將函數(shù)按照用戶提供的優(yōu)先級(jí)排序了,則調(diào)用時(shí)只需依次取出調(diào)用就行了。那么,排序準(zhǔn)則是什么呢?如你所知,一個(gè)signal對(duì)象sig允許注冊(cè)這樣一些函數(shù):
?
sig.connect(&f0);?//f0沒(méi)有優(yōu)先級(jí)
sig.connect(1,&f1);//f1的優(yōu)先級(jí)為1
sig.connect(2,&f2);?//f2的優(yōu)先級(jí)為2
sig.connect(&f3);?//f3沒(méi)有優(yōu)先級(jí)
?
這時(shí)候,這四個(gè)函數(shù)的順序是f1,f2,f0,f3。準(zhǔn)則這樣的,如果用戶為某個(gè)函數(shù)提供了一個(gè)優(yōu)先級(jí),如1,2等,則按優(yōu)先級(jí)排序,如果沒(méi)有提供,則相應(yīng)函數(shù)追加在當(dāng)前函數(shù)隊(duì)列的尾部。這樣的排序準(zhǔn)則如何實(shí)現(xiàn)呢,很簡(jiǎn)單,只需要將一個(gè)仿函數(shù)提供給multimap來(lái)比較它的鍵,multimap自己會(huì)排序妥當(dāng),這個(gè)仿函數(shù)如下:
?
摘自boost/signals/detail/signal_base.hpp
template<typename?Compare,?typename?Key>
????class?any_bridge_compare {
?????????????...
?????????//slot管理器的鍵類型為any,所以該仿函數(shù)的兩個(gè)參數(shù)類型都是any
????????bool?operator()(const?any& k1,?const?any& k2)?const
????????{
??????????//如果k1沒(méi)有提供鍵(如f0,它的鍵any是空的)則它處于任何鍵之后
??????????if?(k1.empty())
????????????return?false;
??????????//如果k2沒(méi)有提供鍵,則任何鍵都排在它之前
??????????if?(k2.empty())
????????????return?true;
??????????//如果兩個(gè)鍵都存在,則將鍵類型轉(zhuǎn)換為合適的類型再作比較
??????????return?comp(*any_cast<Key>(&k1), *any_cast<Key>(&k2));
????????}
??????private:
????????Compare comp;
};
?
這個(gè)仿函數(shù)就是提供給slot管理器來(lái)將回調(diào)函數(shù)排序的仿函數(shù)。它的比較準(zhǔn)則為:首先看k1是否為空,如果是,則在任何鍵之后。再看k2是否為空,如果是,則任何鍵都在它之前。否則,如果兩者都非空,則再另作比較。并且,從代碼中看出,這最后一次比較又轉(zhuǎn)交給了Compare這個(gè)仿函數(shù),并事先將鍵轉(zhuǎn)型為Key類型(既然非空,就可以轉(zhuǎn)型了)。Key和Compare這兩個(gè)模板參數(shù)都可由用戶定制,如果用戶不提供,則為默認(rèn)值:Key=int,Compare=std::less<int>。
?
現(xiàn)在你大概已經(jīng)明白為什么slot管理器要以any作為其鍵(key)類型了,正是為了實(shí)現(xiàn)“如果用戶不指定優(yōu)先級(jí),則優(yōu)先級(jí)最低”的語(yǔ)義。試想,如果用戶指定什么類型,slot管理器的鍵就是什么類型——如int,那么哪個(gè)值才能表示“最低優(yōu)先級(jí)”這個(gè)概念呢?正如int里面沒(méi)有值可以表現(xiàn)“負(fù)無(wú)窮大”的概念一樣,這是不可能的。但是,如果用一個(gè)指針來(lái)指向這個(gè)值,那么當(dāng)指針空著的時(shí)候,我們就可以說(shuō)“這是個(gè)特殊的值”,本例中,這個(gè)特殊值就代表“優(yōu)先級(jí)最低”,而當(dāng)指針?lè)强諘r(shí),我們?cè)賮?lái)作真正的比較。況且,any是個(gè)特殊的指針,你可以以類型安全的方式(通過(guò)一個(gè)any_cast<>)從中取出你先前保存的任何值(如果類型不符,則會(huì)拋出異常)。
?
回顧上面的例子,對(duì)于f0,f3沒(méi)有提供相應(yīng)的鍵,從而構(gòu)造了一個(gè)空的any()對(duì)象,根據(jù)前面所講的比較準(zhǔn)則,其“優(yōu)先級(jí)最低”,并且,由于f3較晚注冊(cè),所以在最末端(想想前面描述的比較準(zhǔn)則)。
?
當(dāng)然,用戶也可以定制
Key=std::string,
Compare=std::greater<std::string>。
總之一切按你的需求。
?
回調(diào)
下面要分析的就是回調(diào)了。回調(diào)函數(shù)已經(jīng)連接到signal,而觸發(fā)signal的方式很簡(jiǎn)單,由于signal本身就是一個(gè)函數(shù)對(duì)象,所以可以這樣:
?
?????signal<int(int,double)> sig;
?????sig.connect(&f1);
?????sig.connect(&f2);
?????int ret=sig(0,3.14);?//正如調(diào)用普通函數(shù)一樣
?
前面提到過(guò),signal允許用戶定制其返回策略(即,返回最大值,或最小值等),默認(rèn)情況下,signal返回所有回調(diào)函數(shù)的返回值中的最后一個(gè)值,這通過(guò)一個(gè)模板參數(shù)來(lái)實(shí)現(xiàn),在signal的模板參數(shù)中有一個(gè)名為Combiner,是一個(gè)仿函數(shù),默認(rèn)為:
?
typename Combiner = last_value<R>
?
last_value是個(gè)仿函數(shù),它有兩個(gè)參數(shù),均為迭代器,它從頭至尾遍歷這兩個(gè)迭代器所表示的區(qū)間,并返回最后一個(gè)值,算法定義如下:
?
?????摘自boost/last_value.hpp
????T?operator()(InputIterator first, InputIterator last)?const
????{
??????T value = *first++;
??????while?(first != last)
????????value = *first++;
??????return?value;
}
?
我本以為signal會(huì)以一個(gè)簡(jiǎn)潔的for_each遍歷slot管理器,輔以一個(gè)仿函數(shù)來(lái)調(diào)用各個(gè)回調(diào)函數(shù),并將它們的返回值緩存為一個(gè)序列,而first和last正指向該序列的頭尾。然后在該序列上應(yīng)用該last_value算法(返回策略),從而返回恰當(dāng)?shù)闹怠_@豈非很自然?
?
但是很明顯,將各個(gè)回調(diào)函數(shù)的返回值緩存為一個(gè)序列需要消耗額外的空間和時(shí)間,況且我在signal的operator()操作符的源代碼里只發(fā)現(xiàn)一行!就是將last_value應(yīng)用于一個(gè)區(qū)間。在此之前找不到任何代碼是遍歷slot管理器并一一調(diào)用回調(diào)函數(shù)的。但回調(diào)函數(shù)的確被一一調(diào)用了,只不過(guò)方式很巧妙,也很隱藏,并且更簡(jiǎn)潔。繼續(xù)往下看。
?
從某種程度上說(shuō),參數(shù)first指向slot管理器(multimap)的區(qū)間頭,而last指向其尾部。但是,既然該仿函數(shù)名為last_value,那么直接返回*(--last)豈不更省事?為何非要在區(qū)間上每前進(jìn)一步都要對(duì)迭代器解引用呢(這很關(guān)鍵,后面會(huì)解釋)?況且,函數(shù)調(diào)用又在何處呢?slot管理器內(nèi)保存的只不過(guò)是一個(gè)個(gè)函數(shù),遍歷它,取出函數(shù)又有何用?問(wèn)題的關(guān)鍵在于,first并非單純的只是slot管理器的迭代器,而是一個(gè)iterator_adapter,也就是說(shuō),它將slot管理器(multimap)的迭代器封裝了一下,從而對(duì)它解引用的背后其實(shí)調(diào)用了函數(shù)。有點(diǎn)迷惑?接著往下看:
?
iterator_facade(iterator_adapter)
iterator_facade(iterator_adapter)在boost庫(kù)里面是一個(gè)獨(dú)立的組件,其功能是創(chuàng)建一個(gè)具有iterator外觀(語(yǔ)義)的類型,而該iterator的具體行為卻又完全可以由用戶自己定制。具體用法請(qǐng)參考boost庫(kù)的官方文檔。這里我們只簡(jiǎn)單描述其用途。
?
上面提到,傳遞給last_value<>仿函數(shù)的兩個(gè)迭代器是經(jīng)過(guò)封裝的,如何封裝呢?這兩個(gè)迭代器的類型為slot_call_iterator,這正是個(gè)iterator_adapter,其代碼如下:
?
摘自boost/signals/detail/slot_call_iterator.hpp
?????template<typename?Function,?typename?Iterator>
class?slot_call_iterator?//參數(shù)first的類型其實(shí)是這個(gè)
?????????:public?iterator_facade<...>
{
?????...
?????????dereference()?const
????????{
??????????????return?f(*iter);?//調(diào)用iter所指向的函數(shù)
????}
};
?
iterator_facade是個(gè)模板類,其中定義了迭代器該有的一切行為如:operator ++,operator --,operator *等,但是具體實(shí)施該行為的卻是其派生類(這里為slot_call_iterator),因?yàn)?/span>iterator_facade會(huì)將具體動(dòng)作轉(zhuǎn)交給其派生類來(lái)執(zhí)行,比如,operator*()在iterator_facade中就是這樣定義的:
?
reference?operator*()?const
????{
?????????//轉(zhuǎn)而調(diào)用派生類的dereference()函數(shù)
?????????return?this->derived().dereference();
}
?
而派生類的dereference()函數(shù)在前面已經(jīng)列出了,其中只有一行代碼:return f(*iter),iter自然是指向slot管理器內(nèi)部的迭代器了,*iter返回的值當(dāng)然是connection_slot_pair[8],下面只需要取出這個(gè)pair中的second成員[9],然后再調(diào)用一下就行了。但是為什么這里的代碼卻是f(*iter),f是個(gè)什么東東?在往下跟蹤會(huì)發(fā)現(xiàn),事實(shí)上,f保存了觸發(fā)signal時(shí)提供的各個(gè)參數(shù)(在上面的例子中,是0和3.14)而f其實(shí)是個(gè)仿函數(shù),f(*iter)其實(shí)調(diào)用了它重載的operator(),后者才算完成了對(duì)slot的真正調(diào)用,代碼如下:
?
?????摘自boost/signals/signal_template.hpp:
????R?operator()(const?Pair& slot)?const
????{
????????F* target =?const_cast<F*>(any_cast<F>(&slot.second.second[10]));
????????return?(*target)(args->a1,args->a2);//真正的調(diào)用在這里!!!
}
?
這兩行代碼應(yīng)該很好理解:首先取出保存在slot管理器(multimap)中的function(通過(guò)一個(gè)any_cast<>),然后調(diào)用它,并將返回值返回。
?
值得說(shuō)明的是,args是f的成員,它是個(gè)結(jié)構(gòu)體,封裝了調(diào)用參數(shù),對(duì)于本例,它有兩個(gè)成員a1,a2,分別保存的是signal的兩個(gè)參數(shù)(0和3.14)。而類型F對(duì)于本例則為boost::function<int(int,double)>[11],這正是slot管理器內(nèi)所保存的slot類型,前面已經(jīng)提到,這個(gè)slot由connection_pair里面的second(一個(gè)any類型的泛型指針)來(lái)持有,所以這里出現(xiàn)了any_cast<>,以還其本來(lái)面目。
?
所以說(shuō),slot_call_iterator這個(gè)迭代器的確是在遍歷slot管理器,但是對(duì)它解引用其實(shí)就是在調(diào)用當(dāng)前指向的函數(shù),并返回其返回值。了解到這一點(diǎn),再回顧一下last_value的代碼,就不難理解為什么其算法代碼中要步步解引用了——原來(lái)是在調(diào)用函數(shù)!
?
簡(jiǎn)而言之,signal的這種調(diào)用方式是“一邊迭代一邊調(diào)用一邊應(yīng)用返回策略”,三管齊下。
?
“這太復(fù)雜了”你抱怨說(shuō):“能不能先遍歷slot管理器,依次調(diào)用其內(nèi)部的回調(diào)函數(shù),然后再應(yīng)用返回策略呢?”。答案是當(dāng)然能,只不過(guò)如果那樣,就必須先將回調(diào)函數(shù)的返回值緩存為一個(gè)序列,這樣才能在其上應(yīng)用返回策略。哪有三管齊下來(lái)得精妙?
?
現(xiàn)在,你可以為signal定制返回策略了,具體的例子參考libs/signals/test/signal_test.cpp。
?
后記
本文我們只分析了signal的大致架構(gòu)。雖然內(nèi)容甚多,但其實(shí)只描述了signal的小部分。其中略去了很多技術(shù)性的細(xì)節(jié),例如slot管理器內(nèi)保存的函數(shù)對(duì)象為什么要用any來(lái)持有。而不直接為function<...>,還有slot管理器里的調(diào)用深度管理——即如果某個(gè)回調(diào)函數(shù)要斷開(kāi)自身與signal的連接該如何處理。還有,對(duì)slot_call_iterator解引用時(shí)其實(shí)將函數(shù)調(diào)用的返回值緩存了起來(lái)(文中列出的代碼為簡(jiǎn)單起見(jiàn),直接返回了該返回值),如何緩存,為什么要緩存?還有,為什么basic_connection中的signal和signal_data成員的類型都是void*?還有signal中所用到的種種泛型技術(shù)等等。
?
當(dāng)然,細(xì)節(jié)并非僅僅是細(xì)節(jié),很多精妙的東西就隱藏在細(xì)節(jié)中。另外,我們沒(méi)有分析slot類的用處——不僅僅作為中間層。最后,一個(gè)最大的遺留問(wèn)題是:如果注冊(cè)的是函數(shù)對(duì)象,如何跟蹤其析構(gòu),這是個(gè)繁雜而精妙的過(guò)程,需要篇幅甚多。
?
這些都會(huì)在下篇——高級(jí)篇中一一水落石出。
?
目錄(展開(kāi)《boost源碼剖析》系列文章)
?
[1]?double(double)是個(gè)類型,函數(shù)類型。所謂函數(shù)類型可以看作將函數(shù)指針類型中的’(*)’去掉后得到的類型。事實(shí)上,函數(shù)類型在面臨拷貝語(yǔ)義的上下文中會(huì)退化為函數(shù)指針類型。
[2]?boost庫(kù)的源代碼可從sf.boost.org網(wǎng)站獲得。目前的版本是1.31.0。
[3]?boost庫(kù)的源代碼里面宏和泛型用得極多,的確很難讀懂,所以本文中列出的代碼都是簡(jiǎn)單起見(jiàn),只取出其中最關(guān)鍵的部分。所有的宏都已展開(kāi),所以,有些代碼與boost庫(kù)里的源代碼有些不同。
[4]?boost::function是個(gè)泛型的函數(shù)指針類,例如boost::function<int(int,double)>可以指向任何類型為int(int,double)的函數(shù)或仿函數(shù)。
[5]?boost::any也是boost庫(kù)的一員。它是個(gè)泛型指針,可以指向任意對(duì)象,并可以從中以類型安全的方式取出保存的對(duì)象。
[6]?顧名思義,即帶有共享計(jì)數(shù)的指針,boost庫(kù)里面有很多種智能指針,此其一
[7]?slots_是signal_base_impl中的唯一的數(shù)據(jù)成員,也就是slot管理器。請(qǐng)回顧前面的源代碼。
[8]?這里為了簡(jiǎn)單起見(jiàn)才這樣講,其實(shí)*iter返回的類型是std::pair<any,conection_slot_pair>,any為multimap的鍵(key)類型。
[9]?前面已經(jīng)提到過(guò),這個(gè)second成員是個(gè)any類型的泛型指針,指向的其實(shí)就是boost::function,后者封裝了用戶注冊(cè)的函數(shù)或函數(shù)對(duì)象。
[10]?前面已經(jīng)提到了,slot管理器內(nèi)保存的是個(gè)connection_slot_pair,其second成員是any,指向boost::function對(duì)象,而slot管理器本身是個(gè)multimap,所以其中保存的值類型是std::pair<any,conection_slot_pair>,*iter返回的正是這個(gè)類型的值,所以這里需要取兩次second成員:slot.second.second。
[11]?再次提醒,boost::function<int(int,double)>可以指向任何類型為int(int,double)的函數(shù)或函數(shù)對(duì)象,本例中,f1,f2,f3,f4函數(shù)的類型都是int(int,double)。
總結(jié)
以上是生活随笔為你收集整理的boost源码剖析之:多重回调机制signal(上)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: boost源码剖析之:泛型函数指针类bo
- 下一篇: boost源码剖析之:多重回调机制sig