亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

boost源碼剖析之:多重回調(diào)機制signal(下)

系統(tǒng) 1877 0

boost 源碼剖析之:多重回調(diào)機制 signal( )

劉未鵬

C++ 的羅浮宮 (http://blog.csdn.net/pongba)

本文的上篇 中,我們大刀闊斧的剖析了 signal 的架構(gòu)。不過還有很多精微之處沒有提到,特別是一個遺留問題還沒有解決:如果用戶注冊的是函數(shù)對象(仿函數(shù)), signal 又當如何處理呢?

下篇:高級篇

概述

在本文的上篇中,我們已經(jīng)分析了 signal 的總體架構(gòu)。至于本篇,我們則主要集中于將 函數(shù)對象 (即仿函數(shù))連接到 signal 的來龍去脈。 signal 庫的作者在這個方面下了很多功夫,甚至可以說,并不比構(gòu)建整個 signal 架構(gòu)的功夫下得少。

之所以為架構(gòu),其中必然隱藏著一些或重要或精妙的思想。

學(xué)過 STL 的人都知道,函數(shù)對象 [1] (function object) STL 中的重要概念和基石之一。它使得一個對象可以像函數(shù)一樣被 調(diào)用 ,而調(diào)用形式又是與函數(shù)一致的。這種一致性在泛型編程中乃是非常重要的,它意味著 泛化 ,而這正是泛型世界所有一切的基礎(chǔ)。而函數(shù)對象又由于其攜帶的信息較之普通函數(shù)大為豐富,從而具有更為強大的能力。

所以 signal 簡直是 不得不 支持函數(shù)對象。然而函數(shù)對象又和普通函數(shù)不同:函數(shù)對象會析構(gòu)。問題在于:如果某個函數(shù)對象連接到 signal ,那么,該函數(shù)對象析構(gòu)時,連接是否應(yīng)該斷開呢?這個問題, signal 的設(shè)計者留給用戶來選擇:如果用戶覺得函數(shù)對象一旦析構(gòu),相應(yīng)的連接也應(yīng)該自動斷開,則可以將其函數(shù)對象派生自 boost::signals::trackable 類,意即該對象是 可跟蹤 的。反之則不用作此派生。這種跟蹤對象析構(gòu)的能力是很有用的,在某些情況下,用戶需要這種語義:例如,一個負責(zé)數(shù)據(jù)庫訪問及更新的函數(shù)對象,而該對象的生命期受某個管理器的管理,現(xiàn)在,將它連接到某個代表用戶界面變化的 signal ,那么,當該對象的生命期結(jié)束時,對應(yīng)的連接顯然應(yīng)該斷開 —— 因為該對象的析構(gòu)意味著對應(yīng)的數(shù)據(jù)庫不再需要更新了。

signal 庫支持跟蹤函數(shù)對象析構(gòu)的方式很簡單,只要將被跟蹤的函數(shù)對象派生自 boost::signals::trackable 類即可,不需要任何額外的步驟。解剖這個 trackable 類所隱藏的秘密正是本文的重點。

架構(gòu)

很顯然, trackable 類是整個問題的關(guān)鍵。將函數(shù)對象派生自該類,就好比為函數(shù)對象安上了一個 跟蹤器 。根據(jù) C++ 語言的規(guī)則,當某個對象析構(gòu)時,先析構(gòu)派生層次最高 (most derived) 的對象,再逐層往下析構(gòu)其子對象。這就意味著,函數(shù)對象的析構(gòu)最終將會導(dǎo)致其基類 trackable 子對象的析構(gòu),從而在后者的析構(gòu)函數(shù)中,得到斷開連接的機會。那么,哪些連接該斷開呢?換句話說,該斷開與哪些 signal 的連接呢?當然是該函數(shù)對象連接到的 signals 。而這些連接則全部保存在一個 list 里面。下面就是 trackable 的代碼:

class trackable {

typedef std::list<connection> connection_list;

typedef connection_list::iterator connection_iterator;

mutable connection_list connected_signals ;

...

}

connected_signals 是個 list ,其中保存的是該函數(shù)對象所連接到的 signals 。只不過是以 connection 的形式來表示的。這些 connection 都是 控制性 [2] 的,一旦析構(gòu)則自動斷開連接。所以, trackable 析構(gòu)時根本不需要任何額外的動作,只要讓該 list 自行析構(gòu)就行了。

了解了這一點,就可以畫出可跟蹤的函數(shù)對象的基本結(jié)構(gòu),如 圖四

圖四

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 204.75pt; HEIGHT: 233.25pt" type="#_x0000_t75"><imagedata o:title="boost" src="file:///C:/DOCUME~1/pongba/LOCALS~1/Temp/msohtml1/01/clip_image001.gif"></imagedata></shape>

boost源碼剖析之:多重回調(diào)機制signal(下)

現(xiàn)在的問題是,每當該函數(shù)對象連接到一個 signal ,都會將相應(yīng) connection 的一個副本插入到其 trackable 子對象的 connected_signals 成員 ( 一個 list) 中去。然而,這個插入究竟發(fā)生在何時何地呢?

在本文的上篇中曾經(jīng)分析過連接的過程。對于函數(shù)對象,這個過程仍然是一樣。不過,當時略過了一些細節(jié),這些細節(jié)正是與函數(shù)對象相關(guān)的。現(xiàn)在一一道來:

如你所知,在將函數(shù) ( 對象 ) 連接到 signal 時,函數(shù) ( 對象 ) 會先被封裝成一個 slot 對象, slot 類的構(gòu)造函數(shù)如下:

slot(const F& f):slot_function(get_invocable_slot(f,tag_type(f)))

{

// 一個 visitor ,用于訪問 f 中的每個 trackable 子對象

bound_objects_visitor do_bind( bound_objects );

// 如果 f 為函數(shù)對象,則訪問 f 中的每一個 trackable 子對象

visit_each (do_bind,get_inspectable_slot [3] (f,tag_type(f)));

// 創(chuàng)建一個 connection ,表示 f 與該 slot 的連接,這是為了實現(xiàn) “delayed-connect”

create_connection();

}

bound_objects slot 類的成員,其類型為 vector<const trackable*> ??上攵?,經(jīng)過第二行代碼 “visit_each(...)” 的調(diào)用,該 vector 中保存的將是指向 f 中的各個 trackable 子對象的指針。

等等! 你敏銳的發(fā)現(xiàn)了一個問題: 前面不是說過,如果用戶要讓他的函數(shù)對象成為可跟蹤的,則將該函數(shù)對象派生自 trackable 對象嗎?那么,也就是說,如果 f 是個 可跟蹤 的函數(shù)對象,那么其中的 trackable 子對象當然只有一個(基類對象)!但為什么這里 bound_objects 的類型卻是一個 vector 呢?單單一個 trackable* 不就夠了么?

在分析這個問題之前,我們先來看一段例子代碼:

struct S1:boost::signals::trackable

{// 該對象是可跟蹤的!但并非一個函數(shù)對象

void test(){cout<<"test/n";}

};

...

boost::signal< void ()> sig;

{ // 一個局部作用域

S1 s1;

sig.connect( boost::bind(&S1::test,boost::ref(s1)) );

sig(); // 輸出 “test”

} // 結(jié)束該作用域 ,s1 在此析構(gòu),斷開連接

sig(); // 無輸出

boost::bind() &S1::test [4] “this” 參數(shù)綁定為 s1 ,從而生成一個 “void()” 型的仿函數(shù),每次調(diào)用該仿函數(shù)就相當于調(diào)用 s1.test() ,然而,這個仿函數(shù)本身并非可跟蹤的,不過,很顯然,這里的 s1 對象一旦析構(gòu),則該仿函數(shù)就失去了意義,從而應(yīng)該讓連接斷開。所以,我們應(yīng)該使 S1 類成為可跟蹤的(見 struct S1 的代碼)。

然而,這又能說明什么呢?仍然只有一個 trackable 子對象!但是,答案已經(jīng)很明顯了:既然 boost::bind 可以綁定一個參數(shù),難道不能綁定兩個參數(shù)?對于一個延遲調(diào)用的函數(shù)對象 [5] ,一旦其某個按引用語義傳遞的參數(shù)析構(gòu)了,該函數(shù)對象也就相應(yīng)失效了。所以,對于這種函數(shù)對象,其按引用傳遞的參數(shù)都應(yīng)該是可跟蹤的。在上例中, s1 就是一個按引用傳遞的參數(shù) [6] ,所以是可跟蹤的。所以,如果有多個這種參數(shù)綁定到一個仿函數(shù),就會有多個 trackable 對象,其中任意一個對象的析構(gòu)都會導(dǎo)致仿函數(shù)失效以及連接的斷開。

例如,假設(shè) C1,C2 類都是 trackable 的。并且函數(shù) test 的類型為 void(C1,C2) 。那么 boost::bind(&test,boost::ref(c1),boost::ref(c2)) 就會返回一個 void() 型的函數(shù)對象,其中 c1,c2 作為 test 的參數(shù)綁定到了該函數(shù)對象。這時候,如果 c1 c2 析構(gòu),這個函數(shù)對象也就失效了。如果先前該函數(shù)對象曾連接到某個 signal<void()> 型的 signal ,則連接應(yīng)該斷開。

問題在于,如何獲得綁定到某個函數(shù)對象的所有 trackale 子對象呢?

關(guān)鍵在于 visit_each 函數(shù) —— 我們回到 slot 的構(gòu)造函數(shù)(見上文列出的源代碼),其第二行代碼調(diào)用了 visit_each 函數(shù),該函數(shù)負責(zé)訪問 f 中的各個 trackable 子對象,并將它們的地址保存在 bound_objects 這個 vector 中。

至于 visit_each 是如何訪問 f 中的各個 trackable 子對象的,這并非本文的重點,我建議你自行參考源代碼。

slot 類的構(gòu)造函數(shù)最后調(diào)用了 create_connection 函數(shù),這個函數(shù)創(chuàng)建一個連接對象,表示函數(shù)對象和該 slot 的連接。 咦?為什么和 slot 連接,函數(shù)對象不是和 signal 連接的嗎? 沒錯。但這個看似蛇足的舉動其實是為了實現(xiàn) “delayed connect” ,例如:

void delayed_connect(Functor* f)

{

// 構(gòu)造一個 slot ,但暫時不連接

slot_type slot(*f);

// 使用 f 做一些事情,在這個過程中 f 可能會被析構(gòu)掉

...

// 如果 f 已經(jīng)被析構(gòu)了,則 slot 變?yōu)? inactive 態(tài),則下面的連接什么事也不做

sig.connect(slot);

}

...

Functor* pf= new Functor();

delayed_connect(pf);

...

這里,如果在 slot 連接到 sig 之前, f“ 不幸 析構(gòu)了,則連接不會生效,只是返回一個空連接。

為了達到這個目的, slot 類的構(gòu)造函數(shù)使用 create_connection 構(gòu)造一個連接,這個連接其實沒有實際意義,只是用于 監(jiān)視 函數(shù)對象是否析構(gòu)。如果函數(shù)對象析構(gòu)了,則該連接會變?yōu)? 斷開 態(tài)。下面是 create_connection 的源代碼:

摘自 libs/signals/src/slot.cpp

void slot_base::create_connection()

{

basic_connection* con = new basic_connection();

con->signal = static_cast < void *>( this );

con->signal_data = 0;

con->signal_disconnect = &bound_object_destructed;

watch_bound_objects.reset(con);

...

}

這段代碼先 new 了一個連接,并將其三個成員設(shè)置妥當。由于該連接純粹僅作 監(jiān)視 該函數(shù)對象是否析構(gòu)之用,并非真的 連接 slot ,所以 signal_data 成員只需閑置為 0 ,而 signal_disconnect 所指的函數(shù) &bound_object_destructed 也只不過是個什么事也不做的空函數(shù)。關(guān)鍵是最后一行代碼: watch_bound_objects 乃是 slot 類的成員,類型是 connection ,這行代碼使其指向上面新建的 con 連接對象。注意,在后面省略掉的部分代碼中, 該連接的副本也被保存到待連接的函數(shù)對象的各個 trackable 子對象中 (前面已經(jīng)提到(參見圖四),這系保存在一個 list 中),這才真正使得 監(jiān)視 成為可能!因為這樣做了之后,一旦代連接的函數(shù)對象析構(gòu)了,將會導(dǎo)致 con 連接為 斷開 狀態(tài)。從而在 sig.connect(slot) 時可以通過查詢 slot 中的 watch_bound_objects 副本的連接狀態(tài)得知該 slot 是否有效,如果無效,則返回一個空的連接。這里, connection 巧妙的充當了一個 監(jiān)視器 的作用。

說到這里,你應(yīng)該也就明白了為什么 basic_connection signal signal_data 成員的類型為 void* 而不是 signal_base_impl* slot_iterator* —— 是的,因為函數(shù)對象不但連接到 signal ,還 連接 slot 。將這兩個成員類型設(shè)置為 void* 可以復(fù)用該類以使其充當 監(jiān)視器 的角色。 signal 庫的作者真可謂惜墨如金。

回到正題,我們接著考察如何將封裝了函數(shù)對象的 slot 連接到 signal 。這里,我建議你先回顧本文的上篇,因為這與將普通函數(shù)連接到 signal 有很大一部分相同之處,只不過多做了一些額外的工作。

同樣,可想而知的是,這個連接過程仍然是先將 slot 插入到 signal 中的 slot 管理器中去,并將 signal 的地址,插入后指向該 slot 的迭代器的地址,以及負責(zé)斷開連接的函數(shù)地址分別保存到表示本次連接的 basic_connection 對象的三個成員 [7] 中去。這時,故事幾乎已經(jīng)結(jié)束了一半 —— 用戶已經(jīng)可以通過該對象來控制相應(yīng)連接了。但是,注意,只是 用戶 !對于函數(shù)對象來說,不但用戶能夠控制連接,函數(shù)對象也必須能夠 控制 連接,因為它析構(gòu)時必須能夠斷開連接,所以,我們還需要將該連接對象的副本保存到函數(shù)對象的各個 trackable 子對象中去:

摘自 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)

{

... // 創(chuàng)建 basic_connection 對象并設(shè)置其成員

// 下面的 for 循環(huán)將該連接的副本保存到各個 trackable 子對象中

for (std::vector<const trackable*>::const_iterator i =

bound_objects.begin();

i != bound_objects.end();++i)

{

bound_object binding;

(*i)->signal_connected(slot_connection, binding );

con->bound_objects.push_back(binding);

}

...

}

在上面的代碼中, for 循環(huán)遍歷綁定到該函數(shù)對象的各個 trackable 子對象,并將該連接的副本 slot_connection 保存到其中。這樣,當某個 trackable 子對象析構(gòu)時,就會通過保存在其中的副本來斷開該連接,從而達到 跟蹤 的目的。

但是,這里還有個問題:這里實際的連接只有一個,但卻產(chǎn)生了多個副本,分別操縱在各個 trackable 子對象手中,如果用戶愿意,用戶還可以操縱一個或多個副本。但是,一旦該連接斷開 —— 不管是由于某個 trackable 子對象的析構(gòu)還是用戶手動斷開 —— 則保存在各個 trackable 子對象中的該連接的副本都應(yīng)該被刪除掉。不然既占空間又沒有任何意義,還會導(dǎo)致這樣的情況:只要其中有一個 trackable 對象還沒有析構(gòu),表示該連接的 basic_connection 對象就不會被 delete 掉。特別是當連接由用戶斷開時,每個未析構(gòu)的 trackable 對象中都會仍留有一個該連接對象的副本,直到 trackable 對象析構(gòu)時該副本才會被刪除。這就意味著,如果存在一個 長命百歲 trackable 函數(shù)對象,并在其生命期中頻繁被用戶連接到 signal 并頻繁斷開連接,那么,每次連接都會遺留一個連接副本在其 trackable 基類子對象中,這是個巨大的累贅。

那么,這個問題到底如何解決呢? basic_connection 仍然是問題的核心,既然用戶只能通過 connection 對象來控制連接,而 connection 對象實際上完全通過 basic_connection 來操縱連接,那么如何解決這個問題的責(zé)任當然落在 basic_connection 身上 —— 既然它知道哪個函數(shù)(對象)連接到哪個 signal 并在其 slot 管理器中的位置,那么,為什么不能讓它也知道 該連接在各個 trackable 對象中的副本所在何處 呢?

當然可以。答案就在于 basic_connection 的第四個成員 bound_objects ,其定義如下:

std::list<bound_object> bound_objects;

該成員正是用來記錄 該連接在各個 trackable 對象中的副本所在何處 的。它的類型是 std::list ,其中每一個 bound_object 型的對象都代表 某一個連接副本所在之處 。有了它,在斷開連接時,就可以依次刪除各個 trackable 對象中的副本。

那么,這個 bound_objects 又是何時被填充的呢?當然是在連接時,因為只有在連接時才知道有幾個 trackable 對象,并有機會將副本保存到它們內(nèi)部。我們回顧上文的 connect_slot 函數(shù)的代碼,其中有加底紋的部分剛才沒有分析,這正是與此相關(guān)的。為了清晰起見,我們將分析以源代碼注釋的形式寫出來:

//bound_object 對象保存的是連接副本在 trackable 對象中的位置

bound_object binding;

// 調(diào)用的是 trackable::signal_connected 函數(shù),該函數(shù)告訴 trackable 對象它已經(jīng)連接到了 signal ,并提供連接的副本(第一個參數(shù)),該函數(shù)會將該副本插入到 trackable 的成員 connected_signals (見篇首 trackable 類的代碼)中去。并將插入的位置反饋給 binding 對象(第二個參數(shù),按引用傳遞),這時候,通過 binding 就能夠?qū)⒃摳北緩? trackable 對象中刪除。

(*i)->signal_connected(slot_connection, binding );

// 將接受反饋后的 binding 對象保存到該連接的 bound_objects 成員中去,以便以后通過它來刪除連接的副本

con->bound_objects.push_back(binding);

要想完全搞清楚以上幾行代碼,我們還得來看看 bound_object 類的結(jié)構(gòu)以及 trackable::signal_connected 到底干了些什么?先來看看 bound_object 的結(jié)構(gòu):

摘自 boost/signals/connection.hpp

struct bound_object {

void * obj;

void * data;

void (*disconnect)( void *, void *);

}

發(fā)現(xiàn)什么特別的沒有?是的,它的結(jié)構(gòu)簡直就是 basic_connection 的翻版,只不過成員的名字不同了而已。 basic_connection 因為是控制連接的樞紐,所以其三個成員表現(xiàn)的是被連接的 slot signal 中的位置。而 bound_object 表現(xiàn)的是 connection 副本在 trackable 對象中的位置。在介紹 bound_object 的三個成員之前,我們先來考察 trackable::signal_connected 函數(shù),因為這個函數(shù)同時也揭示了這三個成員的含義:

摘自 libs/signals/src/trackable.cpp

void trackable::signal_connected(connection c,

bound_object& binding )

{

// connection 副本插入到 trackable 對象中的 connected_signals 中去, connected_signals 是個 std::list<connection> 型的容器,負責(zé)跟蹤該對象連接到了哪些 signal (見篇首的詳述)。

connection_iterator pos =

connected_signals.insert(connected_signals.end(), c);

// 將該 trackable 對象中保存的 connection 副本設(shè)置為 控制性 的,從而該副本析構(gòu)時才會自動斷開連接。

pos->set_controlling();

//obj 指針指向 trackable 對象,注意這里將 trackable* 轉(zhuǎn)型為 void* 以利于保存。

binding.obj = const_cast < void *>( reinterpret_cast < const void *>( this ));

//data 指向 connection 副本在 connected_signals 容器中的位置,注意這里的轉(zhuǎn)型

binding.data = reinterpret_cast < void *>( new connection_iterator(pos));

// 通過這個函數(shù)指針,可以將這個 connection 副本刪除: signal_disconnected 函數(shù)接受 obj data 為參數(shù),將 connection 副本 erase

binding.disconnect = & signal_disconnected ;

}

分析完了這段代碼, bound_object 類的三個成員的含義不言自明。注意,其最后一個成員是個函數(shù)指針,指向 trackable::signal_disconnected 函數(shù),這個函數(shù)負責(zé)將一個 connection 副本從某個 trackable 對象中刪除,其參數(shù)有二,正是 bound_object 的前兩個成員 obj data ,它們合起來指明了一個 connection 副本的位置。

當這些副本在各個 trackable 子對象中都安置妥當后,連接就算完成了。我們再來看看連接具體是如何斷開的,對于函數(shù)對象,斷開它與某個 signal 的連接的過程大致如下:首先,與普通函數(shù)一樣,將函數(shù)對象從 signal slot 管理器中 erase 掉,這個連接就算斷開了。其次就是只與函數(shù)對象相關(guān)的動作了:將保存在綁定到函數(shù)對象的各個 trackable 子對象中的 connection 副本清除掉。這就算完成了斷開 signal 與函數(shù)對象的連接的過程。當然,得看到代碼心里才踏實,下面就是:

void connection::disconnect()

{

if ( this ->connected()) {

shared_ptr<detail::basic_connection> local_con = con;

// 先將該函數(shù)指針保存下來

void (*signal_disconnect)( void *, void *) =

local_con->signal_disconnect;

// 然后再將該函數(shù)指針置為 0 ,表示該連接已斷開

local_con->signal_disconnect = 0;

// 斷開連接, signal_disconnect 函數(shù)指針指向 signal_base_impl::slot_disconnected 函數(shù),該函數(shù)在本文的上篇已作了詳細介紹

signal_disconnect(local_con->signal, local_con->signal_data);

// 清除保存在各個 trackable 子對象中的 connection 副本

typedef std::list<bound_object>::iterator iterator;

for (iterator i = local_con->bound_objects.begin();

i != local_con->bound_objects.end(); ++i) {

// 通過 bound_object 的第三個成員, disconnect 函數(shù)指針來清除該連接的每個副本

i->disconnect(i->obj, i->data);

}

}

}

前面已經(jīng)說過, bound_object 的第三個成員 disconnect 指向的函數(shù)為 trackable::signal_disconnected ,顧名思義, “signal” 已經(jīng) “disconnected” 了,該是清除那些多余的 connection 副本的時候了,所以,上面的最后一行代碼 “i->disconnect(...)” 就是調(diào)用該函數(shù)來做最后的清理工作的:

摘自 libs/signals/src/trackable.cpp

void trackable::signal_disconnected( void * obj, void * data)

{

// 將兩個參數(shù)轉(zhuǎn)型,還其本來面目

trackable* self = reinterpret_cast <trackable*>(obj);

connection_iterator* signal =

reinterpret_cast <connection_iterator*>(data);

if (!self->dying) {

// connection 副本 erase

self->connected_signals.erase(*signal);

}

delete signal;

}

這就是故事的全部。這個清理工作一完成,函數(shù)對象與 signal 就再無瓜葛,從此分道揚鑣?;剡^頭來再看看 signal 庫對函數(shù)對象所做的工作,可以發(fā)現(xiàn),其主要圍繞著 trackable 類的成員 connected_signals basic_connection 的成員 bound_objects 而展開。這兩個一個負責(zé)保存 connection 的副本以作跟蹤之用,另一個則負責(zé)在斷開連接時清除 connection 的各個副本。

分析還屬其次,重要的是我們能夠從中汲取到一些納為己用的東西。關(guān)于 trackable 思想,不但可以用在 signal 中,在其它需要跟蹤對象析構(gòu)語義的場合也大可用上。這種架構(gòu)之最妙之處就在于用戶只要作一個簡單的派生,就獲得了完整的對象跟蹤能力,一切的一切都在背后嚴密的完成。

蛇足 & 再談?wù){(diào)用

還記得在本文的上篇分析的 調(diào)用 部分嗎?庫的作者藉由一個所謂的 “slot_call_iterator” 來完成遍歷 slot 管理器和調(diào)用 slot 的雙重任務(wù)。 slot_call_iterator slot 管理器本身的 iterator 語義幾乎相同,只不過對前者解引用 (dereference ,即 “*iter”) 的背后其實調(diào)用了其指向的 slot 函數(shù),并且返回的是 slot 函數(shù)的返回值。這種特殊的語義使得 signal 可以將 slot_call_iterator 直接交給用戶制定的返回策略(如 max_value<> , min_value<> 等),一石二鳥。但是這里面有一個難以察覺的漏洞:一個設(shè)計得不好的算法可能會使迭代器在相同的位置上出現(xiàn)冗余的解引用,例如,一個設(shè)計的不好的 max_value<> 可能會像這樣:

T max = *first++;

for (; first != last; ++first)

max = ( *first > max)? *first : max;

這個算法本身的邏輯并沒有什么不妥,只不過注意到其中 *first 出現(xiàn)了兩次,這意味著什么?如果按照以前的說法,每一次解引用都意味著一次函數(shù)調(diào)用的話,那么同一個函數(shù)將被調(diào)用兩次。這可就不合邏輯了。 signal 必須保證每個注冊的函數(shù)有且僅有一次執(zhí)行的機會。

解決這個問題的任務(wù)落在庫的設(shè)計者身上,無論如何,一個普通用戶寫出上面的算法的確是件無可非議的事。一個明顯的解決方案是將函數(shù)的返回值緩存起來,第二次或第 N 次在同一位置解引用時只是從緩存中取值并返回。 signal 庫的設(shè)計者正是采用的這種方法,只不過, slot_call_iterator 將緩存的返回值交給一個 shared_ptr 來掌管。這是因為,用戶可能會拷貝迭代器,以暫時保存區(qū)間中的某個位置信息,在拷貝迭代器時,如果緩存中已經(jīng)有返回值,即函數(shù)已經(jīng)調(diào)用過了,則新的迭代器也因該引用那個緩存。并且,當最后一個引用該緩存的迭代器消失時,就是該緩存被釋放之時,這正是 shared_ptr 用武之地。具體的實現(xiàn)代碼請你自行參考 boost/signals/detail/slot_call_iterator.hpp

值得注意的是, slot_call_iterator 符合 “single pass” (單向遍歷) concept 。對于這種類型的迭代器只能進行兩種操作:遞增和比較。這就防止了用戶寫出不規(guī)矩的返回策略 —— 例如,二分查找(它要求一個隨機迭代器)。如果用戶硬要犯規(guī),就會得到一個編譯錯誤。

由此可見,設(shè)計一個完備的庫不但需要技術(shù),還要無比的細心。

結(jié)語

相對于 C++ 精致的泛型技術(shù)的應(yīng)用來說,其背后隱藏的思想更為重要。在 signal 庫中,泛型技術(shù)的應(yīng)用其實也不可不謂淋漓盡致,但是語言只是工具,重要的是解決問題的思想。從這篇文章可以看出,作者為了構(gòu)建一個功能完備,健壯,某些特性可定制的 signal 架構(gòu)付出了多少努力。雖然某些地方看似簡單,如 connection 對象,但是都是經(jīng)過反復(fù)揣摩,時間檢驗后作出的設(shè)計抉擇。而對于函數(shù)對象,更是以一個 trackable 基類就實現(xiàn)了完備的跟蹤能力。以一個函數(shù)對象來定制返回策略則是符合 policy-based 設(shè)計的精髓。另外還有一些細致入微的設(shè)計細節(jié),本篇并沒有一一分析,一是為了讓文章更緊湊,二是篇幅 —— 只講主要脈絡(luò)文章尚已如此,再加上各個細節(jié)則更是 了得 了,干脆留給你自行理解,你將 boost 的源代碼和本文列出的相應(yīng)部分比較后或會發(fā)現(xiàn)一些不同之處,那些就是我故意省略掉的細節(jié)所在了。對于細節(jié)有興趣的不妨自己分析分析。

目錄 ( 展開 boost 源碼剖析》系列 文章 )



[1] 函數(shù)對象即重載了 operator() 操作符的對象,故而可以以與函數(shù)調(diào)用一致的語法形式來 調(diào)用 。又稱為 functor ,中文譯為 仿函數(shù)

[2] 控制性 是指該 connection 析構(gòu)時會順便將該連接斷開。反之則不然。關(guān)于 控制性 非控制性 connection 的詳細討論見本文的上篇。

[3] get_inspectable_slot() 當且僅當 f 是個 reference_wrapper 時,返回 f.get()—— 即其中封裝的真實的函數(shù)(對象)。其它時候,該函數(shù)調(diào)用等同于 f 。關(guān)于 reference_wrapper 的詳細介紹見 boost 的官方文檔。

[4] &S1::test 為指向成員函數(shù)的指針。其調(diào)用形式為 (this_ptr->*mem_fun_ptr)() (this_ref.*mem_fun_ptr)() ,而從一般語義上說,其調(diào)用形式為 mem_fun_ptr(this_ref) mem_fun_ptr(this_ptr) 。所以, boost::bind 可以將其“第一個”參數(shù)綁定為 s1 對象。

[5] command 模式,其中封裝的 command 對象就是一個延遲調(diào)用的函數(shù)對象,它暫時保存某函數(shù)及其調(diào)用的各個參數(shù),并在恰當?shù)臅r候調(diào)用該函數(shù)。

[6] boost::ref(s1) 生成一個 boost::reference_wrapper<S1>(s1) 對象,其語義與 裸引用 幾乎一樣,只不過具有拷貝構(gòu)造,以及賦值語義,這有點像 java 里面的對象引用。具體介紹見 boost 的官方文檔。

[7] signal 成員指向連接到的 signal signal_data 成員指向該函數(shù)在 signal 中保存的位置(一般為迭代器),而 signal_disconnect 則是個函數(shù)指針,負責(zé)斷開連接,將前兩個成員作為參數(shù)傳給它就可以斷開連接。

boost源碼剖析之:多重回調(diào)機制signal(下)


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦?。?!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 日日免费视频 | 韩国日本一级毛片免费视频 | 午夜影院一区二区 | 99精品国产费观看视频 | 国产成人精品s8sp视频 | 久久99国产精品久久99小说 | 爱爱视频在线免费观看 | 国产高清一区二区三区视频 | 亚洲精品久久久久中文字幕一区 | 欧美 日韩 中字 国产 | 福利在线观看视频 | 免费a黄色 | 国产激情一区二区三区成人91 | 欧美亚洲一区二区三区 | 99久热成人精品视频 | 中文字幕久热精品视频免费 | 一级做a爰片久久毛片唾 | 亚洲无总热门 | 在线播放人成午夜免费视频 | 久久爱com| 日本亚洲精品一区二区三区 | 视频在线一区二区三区 | 国产综合成人久久大片91 | 日本高清视频www夜色资源网 | 欧美一级毛片国产一级毛片 | 思思久热re6这里有精品 | 中日韩欧美中文字幕毛片 | 奇米综合| 亚洲国产视频在线观看 | 日本乱人伦片中文字幕三区 | 欧美在线观看a | 欧美日韩顶级毛片www免费看 | 免费香蕉成视频成人网 | 九九99热久久国产 | 亚洲精品欧美一区二区三区 | 91最新在线 | 国产一区二三区 | 日本亚洲欧美国产日韩ay高清 | 99热这里只有精品免费国产 | aaaaaaa毛片 | 欧美成人一区二区三区在线电影 |