什么是信號(hào)槽?
這個(gè)問題我們可以從兩個(gè)角度來回答,一個(gè)簡短一些,另外一個(gè)則長些。
讓我們先用最簡潔的語言來回答這個(gè)問題——什么是信號(hào)槽?
信號(hào)槽是觀察者模式的一種實(shí)現(xiàn),或者說是一種升華;
一個(gè)信號(hào)就是一個(gè)能夠被觀察的事件,或者至少是事件已經(jīng)發(fā)生的一種通知;
一個(gè)槽就是一個(gè)觀察者,通常就是在被觀察的對(duì)象發(fā)生改變的時(shí)候——也可以說是信號(hào)發(fā)出的時(shí)候——被調(diào)用的函數(shù);
你可以將信號(hào)和槽連接起來,形成一種觀察者-被觀察者的關(guān)系;
當(dāng)事件或者狀態(tài)發(fā)生改變的時(shí)候,信號(hào)就會(huì)被發(fā)出;同時(shí),信號(hào)發(fā)出者有義務(wù)調(diào)用所有注冊(cè)的對(duì)這個(gè)事件(信號(hào))感興趣的函數(shù)(槽)。
信號(hào)和槽是多對(duì)多的關(guān)系。一個(gè)信號(hào)可以連接多個(gè)槽,而一個(gè)槽也可以監(jiān)聽多個(gè)信號(hào)。
信號(hào)可以有附加信息。例如,窗口關(guān)閉的時(shí)候可能發(fā)出 windowClosing 信號(hào),而這個(gè)信號(hào)就可以包含著窗口的句柄,用來表明究竟是哪個(gè)窗口發(fā)出這個(gè)信號(hào);一個(gè)滑塊在滑動(dòng)時(shí)可能發(fā)出一個(gè)信號(hào),而這個(gè)信號(hào)包含滑塊的具體位置,或者新的值等等。我們可以把信號(hào)槽理解成函數(shù)簽名。信號(hào)只能同具有相同簽名的槽連接起來。你可以把信號(hào)看成是底層事件的一個(gè)形象的名字。比如這個(gè) windowClosing 信號(hào),我們就知道這是窗口關(guān)閉事件發(fā)生時(shí)會(huì)發(fā)出的。
信號(hào)槽實(shí)際是與語言無關(guān)的,有很多方法都可以實(shí)現(xiàn)信號(hào)槽,不同的實(shí)現(xiàn)機(jī)制會(huì)導(dǎo)致信號(hào)槽差別很大。信號(hào)槽這一術(shù)語最初來自 Trolltech 公司的 Qt 庫(現(xiàn)在已經(jīng)被 Nokia 收購)。1994年,Qt 的第一個(gè)版本發(fā)布,為我們帶來了信號(hào)槽的概念。這一概念立刻引起計(jì)算機(jī)科學(xué)界的注意,提出了多種不同的實(shí)現(xiàn)。如今,信號(hào)槽依然是 Qt 庫的核心之一,其他許多庫也提供了類似的實(shí)現(xiàn),甚至出現(xiàn)了一些專門提供這一機(jī)制的工具庫。
簡單了解信號(hào)槽之后,我們?cè)賮韽牧硗庖粋(gè)角度回答這個(gè)問題:什么是信號(hào)槽?它們從何而來?
前面我們已經(jīng)了解了信號(hào)槽相關(guān)的概念。下面我們將從更細(xì)致的角度來探討,信號(hào)槽機(jī)制是怎樣一步步發(fā)展的,以及怎樣在你自己的代碼中使用它們。
程序設(shè)計(jì)中很重要的一部分是組件交互:系統(tǒng)的一部分需要告訴另一部分去完成一些操作。讓我們從一個(gè)簡單的例子開始:
// C++
class Button
{
public:
void clicked(); // something that happens: Buttons may be clicked
};
class Page
{
public:
void reload(); // ...which I might want to do when a Button is clicked
};換句話說,Page 類知道如何重新載入頁面(reload),Button 有一個(gè)動(dòng)作是點(diǎn)擊(click)。假設(shè)我們有一個(gè)函數(shù)返回當(dāng)前頁面 currentPage(),那么,當(dāng) button 被點(diǎn)擊的時(shí)候,當(dāng)前頁面應(yīng)該被重新載入。
// C++ --- making the connection directly
void Button::clicked()
{
currentPage()->reload(); // Buttons know exactly what to do when clicked
}這看起來并不很好。因?yàn)?Button 這個(gè)類名似乎暗示了這是一個(gè)可重用的類,但是這個(gè)類的點(diǎn)擊操作卻同 Page 緊緊地耦合在一起了。這使得只要 button 一被點(diǎn)擊,必定調(diào)用 currentPage() 的 reload() 函數(shù)。這根本不能被重用,或許把它改名叫 PageReloadButton 更好一些。
實(shí)際上,不得不說,這確實(shí)是一種實(shí)現(xiàn)方式。如果 Button::click() 這個(gè)函數(shù)是 virtual 的,那么你完全可以寫一個(gè)新類去繼承這個(gè) Button:
// C++ --- connecting to different actions by specializing
class Button
{
public:
virtual void clicked() = 0; // Buttons have no idea what to do when clicked
};
class PageReloadButton : public Button
{
public:
virtual void clicked() {
currentPage()->reload(); // ...specialize Button to connect it to a specific action
}
};好了,現(xiàn)在 Button 可以被重用了。但是這并不是一個(gè)很好的解決方案。
引入回調(diào)
讓我們停下來,回想一下在只有 C 的時(shí)代,我們?cè)撊绾谓鉀Q這個(gè)問題。如果只有 C,就不存在 virtual 這種東西。重用有很多種方式,但是由于沒有了類的幫助,我們采用另外的解決方案:函數(shù)指針。
/* C --- connecting to different actions via function pointers */
void reloadPage_action( void* ) /* one possible action when a Button is clicked */
{
reloadPage(currentPage());
}
void loadPage_action( void* url ) /* another possible action when a Button is clicked */
{
loadPage(currentPage(), (char*)url);
}
struct Button {
/* ...now I keep a (changeable) pointer to the function to be called */
void (*actionFunc_)();
void* actionFuncData_;
};
void buttonClicked( Button* button )
{
/* call the attached function, whatever it might be */
if ( button && button->actionFunc_ )
(*button->actionFunc_)(button->actionFuncData_);
}這就是通常所說的“回調(diào)”。buttonClicked() 函數(shù)在編譯期并不知道要調(diào)用哪一個(gè)函數(shù)。被調(diào)用的函數(shù)是在運(yùn)行期傳進(jìn)來的。這樣,我們的 Button 就可以被重用了,因?yàn)槲覀兛梢栽谶\(yùn)行時(shí)將不同的函數(shù)指針傳遞進(jìn)來,從而獲得不同的點(diǎn)擊操作。
增加類型安全
對(duì)于 C++ 或者 Java 程序員來說,總是不喜歡這么做。因?yàn)檫@不是類型安全的(注意 url 有一步強(qiáng)制類型轉(zhuǎn)換)。
我們?yōu)槭裁葱枰愋桶踩?一個(gè)對(duì)象的類型其實(shí)暗示了你將如何使用這個(gè)對(duì)象。有了明確的對(duì)象類型,你就可以讓編譯器幫助你檢查你的代碼是不是被正確的使用了,如同你畫了一個(gè)邊界,告訴編譯器說,如果有人越界,就要報(bào)錯(cuò)。然而,如果沒有類型安全,你就丟失了這種優(yōu)勢(shì),編譯器也就不能幫助你完成這種維護(hù)。這就如同你開車一樣。只要你的速度足夠,你就可以讓你的汽車飛起來,但是,一般來說,這種速度就會(huì)提醒你,這太不安全了。同時(shí)還會(huì)有一些裝置,比如雷達(dá)之類,也會(huì)時(shí)時(shí)幫你檢查這種情況。這就如同編譯器幫我們做的那樣,是我們出浴一種安全使用的范圍內(nèi)。
回過來再看看我們的代碼。使用 C 不是類型安全的,但是使用 C++,我們可以把回調(diào)的函數(shù)指針和數(shù)據(jù)放在一個(gè)類里面,從而獲得類型安全的優(yōu)勢(shì)。例如:
// re-usable actions, C++ style (callback objects)
class AbstractAction
{
public:
virtual void execute() = 0; // sub-classes re-implement this to actually do something
};
class Button
{
// ...now I keep a (changeable) pointer to the action to be executed
AbstractAction* action_;
};
void Button::clicked()
{
// execute the attached action, whatever it may be
if ( action_ )
action_->execute();
}
class PageReloadAction : public AbstractAction
// one possible action when a Button is clicked
{
public:
virtual void execute() {
currentPage()->reload();
}
};
class PageLoadAction : public AbstractAction
// another possible action when a Button is clicked
{
public:
// ...
virtual void execute() {
currentPage()->load(url_);
}
private:
std::string url_;
};好了!我們的 Button 已經(jīng)可以很方便的重用了,并且也是類型安全的,再也沒有了強(qiáng)制類型轉(zhuǎn)換。這種實(shí)現(xiàn)已經(jīng)可以解決系統(tǒng)中遇到的絕大部分問題了。似乎現(xiàn)在的解決方案同前面的類似,都是繼承了一個(gè)類。只不過現(xiàn)在我們對(duì)動(dòng)作進(jìn)行了抽象,而之前是對(duì) Button 進(jìn)行的抽象。這很像前面 C 的實(shí)現(xiàn),我們將不同的動(dòng)作和 Button 關(guān)聯(lián)起來,F(xiàn)在,我們一步步找到一種比較令人滿意的方法。
多對(duì)多
下一個(gè)問題是,我們能夠在點(diǎn)擊一次重新載入按鈕之后做多個(gè)操作嗎?也就是讓信號(hào)和槽實(shí)現(xiàn)多對(duì)多的關(guān)系?
實(shí)際上,我們只需要利用一個(gè)普通的鏈表,就可以輕松實(shí)現(xiàn)這個(gè)功能了。比如,如下的實(shí)現(xiàn):
class MultiAction : public AbstractAction
// ...an action that is composed of zero or more other actions;
// executing it is really executing each of the sub-actions
{
public:
// ...
virtual void execute();
private:
std::vector<AbstractAction*> actionList_;
// ...or any reasonable collection machinery
};
void MultiAction::execute()
{
// call execute() on each action in actionList_
std::for_each( actionList_.begin(),
actionList_.end(),
boost::bind(&AbstractAction::execute, _1) );
}這就是其中的一種實(shí)現(xiàn)。不要覺得這種實(shí)現(xiàn)看上去沒什么水平,實(shí)際上我們發(fā)現(xiàn)這就是一種相當(dāng)簡潔的方法。同時(shí),不要糾結(jié)于我們代碼中的 std:: 和 boost:: 這些命名空間,你完全可以用另外的類,強(qiáng)調(diào)一下,這只是一種可能的實(shí)現(xiàn),F(xiàn)在,我們的一個(gè)動(dòng)作可以連接多個(gè) button 了,當(dāng)然,也可以是別的 Action 的使用者。現(xiàn)在,我們有了一個(gè)多對(duì)多的機(jī)制。通過將 AbstractAction* 替換成 boost::shared_ptr<AbstractAction>,可以解決 AbstractAction 的歸屬問題,同時(shí)保持原有的多對(duì)多的關(guān)系。
這會(huì)有很多的類!
如果你在實(shí)際項(xiàng)目中使用上面的機(jī)制,很多就會(huì)發(fā)現(xiàn),我們必須為每一個(gè) action 定義一個(gè)類,這將不可避免地引起類爆炸。至今為止,我們前面所說的所有實(shí)現(xiàn)都存在這個(gè)問題。不過,我們之后將著重討論這個(gè)問題,現(xiàn)在先不要糾結(jié)在這里啦!
特化!特化!
當(dāng)我們開始工作的時(shí)候,我們通過將每一個(gè) button 賦予不同的 action,實(shí)現(xiàn) Button 類的重用。這實(shí)際是一種特化。然而,我們的問題是,action 的特化被放在了固定的類層次中,在這里就是這些 Button 類。這意味著,我們的 action 很難被更大規(guī)模的重用,因?yàn)槊恳粋(gè) action 實(shí)際都是與 Button 類綁定的。那么,我們換個(gè)思路,能不能將這種特化放到信號(hào)與槽連接的時(shí)候進(jìn)行呢?這樣,action 和 button 這兩者都不必進(jìn)行特化了。
函數(shù)對(duì)象
將一個(gè)類的函數(shù)進(jìn)行一定曾度的封裝,這個(gè)思想相當(dāng)有用。實(shí)際上,我們的 Action 類的存在,就是將 execute() 這個(gè)函數(shù)進(jìn)行封裝,其他別無用處。這在 C++ 里面還是比較普遍的,很多時(shí)候我們用 ++ 的特性重新封裝函數(shù),讓類的行為看起來就像函數(shù)一樣。例如,我們重載 operator() 運(yùn)算符,就可以讓類看起來很像一個(gè)函數(shù):
class AbstractAction
{
public:
virtual void operator()() = 0;
};
// using an action (given AbstractAction& action)
action();這樣,我們的類看起來很像函數(shù)。前面代碼中的 for_each 也得做相應(yīng)的改變:
// previously
std::for_each( actionList_.begin(),
actionList_.end(),
boost::bind(&AbstractAction::execute, _1) );
// now
std::for_each( actionList_.begin(),
actionList_.end(),
boost::bind(&AbstractAction::operator(), _1) );現(xiàn)在,我們的 Button::clicked() 函數(shù)的實(shí)現(xiàn)有了更多的選擇:
// previously
action_->execute();
// option 1: use the dereferenced pointer like a function
(*action_)();
// option 2: call the function by its new name
action_->operator()();看起來很麻煩,值得這樣做嗎?
下面我們來試著解釋一下信號(hào)和槽的目的?瓷先ィ貙 operator() 運(yùn)算符有些過分,并不值得我們?nèi)ミ@么做。但是,要知道,在某些問題上,你提供的可用的解決方案越多,越有利于我們編寫更簡潔的代碼。通過對(duì)一些類進(jìn)行規(guī)范,就像我們要讓函數(shù)對(duì)象看起來更像函數(shù),我們可以讓它們?cè)谀承┉h(huán)境下更適合重用。在使用模板編程,或者是 Boost.Function,bind 或者是模板元編程的情形下,這一點(diǎn)尤為重要。
這是對(duì)無需更多特化建立信號(hào)槽連接重要性的部分回答。模板就提供了這樣一種機(jī)制,讓添加了特化參數(shù)的代碼并不那么難地被特化,正如我們的函數(shù)對(duì)象那樣。而模板的特化對(duì)于使用者而言是透明的。
松耦合
現(xiàn)在,讓我們回顧一下我們之前的種種做法。
我們執(zhí)著地尋求一種能夠在同一個(gè)地方調(diào)用不同函數(shù)的方法,這實(shí)際上是 C++ 內(nèi)置的功能之一,通過 virtual 關(guān)鍵字,當(dāng)然,我們也可以使用函數(shù)指針實(shí)現(xiàn)。當(dāng)我們需要調(diào)用的函數(shù)沒有一個(gè)合適的簽名,我們將它包裝成一個(gè)類。我們已經(jīng)演示了如何在同一地方調(diào)用多個(gè)函數(shù),至少我們知道有這么一種方法(但這并不是在編譯期完成的)。我們實(shí)現(xiàn)了讓“信號(hào)發(fā)送”能夠被若干個(gè)不同的“槽”監(jiān)聽。
不過,我們的系統(tǒng)的確沒有什么非常與眾不同的地方。我們來仔細(xì)審核一下我們的系統(tǒng),它真正不同的是:
定義了兩個(gè)不同的術(shù)語:“信號(hào)”和“槽”;
在一個(gè)調(diào)用點(diǎn)(信號(hào))與零個(gè)或者多個(gè)回調(diào)(槽)相連;
連接的焦點(diǎn)從提供者處移開,更多地轉(zhuǎn)向消費(fèi)者(也就是說,Button 并不需要知道如何做是正確的,而是由回調(diào)函數(shù)去告知 Button,你需要調(diào)用我)。
但是,這樣的系統(tǒng)還遠(yuǎn)達(dá)不到松耦合的關(guān)系。Button 類并不需要知道 Page 類。松耦合意味著更少的依賴;依賴越少,組件的可重用性也就越高。
當(dāng)然,肯定需要有組件同時(shí)知道 Button 和 Page,從而完成對(duì)它們的連接,F(xiàn)在,我們的連接實(shí)際是用代碼描述的,如果我們不用代碼,而用數(shù)據(jù)描述連接呢?這么一來,我們就有了松耦合的類,從而提高二者的可重用性。
新的連接模式
什么樣的連接模式才算是非代碼描述呢?假如僅僅只有一種信號(hào)槽的簽名,例如 void (*signature)(),這并不能實(shí)現(xiàn)。使用散列表,將信號(hào)的名字映射到匹配的連接函數(shù),將槽的名字映射到匹配的函數(shù)指針,這樣的一對(duì)字符串即可建立一個(gè)連接。
然而,這種實(shí)現(xiàn)其實(shí)包含一些“握手”協(xié)議。我們的確希望具有多種信號(hào)槽的簽名。在信號(hào)槽的簡短回答中我們提到,信號(hào)可以攜帶附加信息。這要求信號(hào)具有參數(shù)。我們并沒有處理成員函數(shù)與非成員函數(shù)的不同,這又是一種潛在的函數(shù)簽名的不同。我們還沒有決定,我們是直接將信號(hào)連接到槽函數(shù)上,還是連接到一個(gè)包裝器上。如果是包裝器,這個(gè)包裝器需要已經(jīng)存在呢,還是我們?cè)谛枰獣r(shí)自動(dòng)創(chuàng)建呢?雖然底層思想很簡單,但是,真正的實(shí)現(xiàn)還需要很好的努力才行。似乎通過類名能夠創(chuàng)建對(duì)象是一種不錯(cuò)的想法,這取決于你的實(shí)現(xiàn)方式,有時(shí)候甚至取決于你有沒有能力做出這種實(shí)現(xiàn)。將信號(hào)和槽放入散列表需要一種注冊(cè)機(jī)制。一旦有了這么一種系統(tǒng),前面所說的“有太多類了”的問題就得以解決了。你所需要做的就是維護(hù)這個(gè)散列表的鍵值,并且在需要的時(shí)候?qū)嵗悺?br />
給信號(hào)槽添加這樣的能力將比我們前面所做的所有工作都困難得多。在由鍵值進(jìn)行連接時(shí),多數(shù)實(shí)現(xiàn)都會(huì)選擇放棄編譯期類型安全檢查,以滿足信號(hào)和槽的兼容。這樣的系統(tǒng)代價(jià)更高,但是其應(yīng)用也遠(yuǎn)遠(yuǎn)高于自動(dòng)信號(hào)槽連接。這樣的系統(tǒng)允許實(shí)例化外部的類,比如 Button 以及它的連接。所以,這樣的系統(tǒng)有很強(qiáng)大的能力,它能夠完成一個(gè)類的裝配、連接,并最終完成實(shí)例化操作,比如直接從資源描述文件中導(dǎo)出的一個(gè)對(duì)話框。既然它能夠憑借名字使函數(shù)可用,這就是一種腳本能力。如果你需要上面所說的種種特性,那么,完成這么一套系統(tǒng)絕對(duì)是值得的,你的信號(hào)槽系統(tǒng)也會(huì)從中受益,由數(shù)據(jù)去完成信號(hào)槽的連接。
對(duì)于不需要這種能力的實(shí)現(xiàn)則會(huì)忽略這部分特性。從這點(diǎn)看,這種實(shí)現(xiàn)就是“輕量級(jí)”的。對(duì)于一個(gè)需要這些特性的庫而言,完整地實(shí)現(xiàn)出來就是一個(gè)輕量級(jí)實(shí)現(xiàn)。這也是區(qū)別這些實(shí)現(xiàn)的方法之一。
信號(hào)槽的實(shí)現(xiàn)實(shí)例—— Qt 和 Boost
Qt 的信號(hào)槽和 Boost.Signals 由于有著截然不同的設(shè)計(jì)目標(biāo),因此二者的實(shí)現(xiàn)、強(qiáng)度也十分不同。將二者混合在一起使用也不是不可能的,我們將在本系統(tǒng)的最后一部分來討論這個(gè)問題。
使用信號(hào)槽
信號(hào)槽是偉大的工具,但是如何能更好的使用它們?相比于直接函數(shù)調(diào)用,有三點(diǎn)值得我們的注意。一個(gè)信號(hào)槽的調(diào)用:
或許會(huì)比直接函數(shù)調(diào)用耗費(fèi)更多的時(shí)間/空間;
可能不能使用 inline;
對(duì)于代碼閱讀者來說可能并不友好。
使用信號(hào)槽進(jìn)行解耦,我們獲得的最大的好處是,連接兩端的對(duì)象不需要知道對(duì)方的任何信息。Button 同動(dòng)作的連接是一個(gè)很典型的案例。例如如下信息:
class Elevator
{
public:
enum Direction { DownDirection=-1, NoDirection=0, UpDirection=1 };
enum State { IdleState, LoadingState, MovingState };
// ...
// signals:
void floorChanged( int newFloor );
void stateChanged( State newState );
void directionChanged( Direction newDirection );
};Elevator 類,也就是電梯,不需要知道有多少顯示器正在監(jiān)聽它的信號(hào),也不需要知道這些顯示器的任何信息。每一層可能有一個(gè)屏幕和一組燈,用于顯示電梯的當(dāng)前位置和方向,另外一些遠(yuǎn)程操控的面板也會(huì)顯示出同樣的信息。電梯并不關(guān)心這些東西。當(dāng)它穿過(或者停在)某一層的時(shí)候,它會(huì)發(fā)出一個(gè) floorChanged(int) 信號(hào);蛟S,交通信號(hào)燈是更合適的一個(gè)例子。
你也可以實(shí)現(xiàn)一個(gè)應(yīng)用程序,其中每一個(gè)函數(shù)調(diào)用都是通過信號(hào)來觸發(fā)的。這在技術(shù)上說是完全沒有問題的,然而卻是不大可行的,因?yàn)樾盘?hào)槽的使用無疑會(huì)喪失一部分代碼可讀性和系統(tǒng)性能。如何在這其中做出平衡,也是你需要考慮的很重要的一點(diǎn)。
Qt 方式
了解 Qt 信號(hào)槽最好的莫過于 Qt 的文檔。不過,這里我們從一個(gè)小例子來了解信號(hào)槽的 Qt 方式的使用。
// Qt Signals and Slots
class Button : public QObject
{
Q_OBJECT
Q_SIGNALS:
void clicked();
};
class Page : public QObject
{
Q_OBJECT
public Q_SLOTS:
void reload();
};
// given pointers to an actual Button and Page:
connect(button, SIGNAL(clicked()), page, SLOT(reload()));Boost.Signals 方式
了解 Boost.Signals 的最好方式同樣是 Boost 的文檔。這里,我們還是先從代碼的角度了解一下它的使用。
// Boost.Signals
class Button
{
public:
boost::signal< void() > clicked;
};
class Page
{
public:
void reload();
};
// given pointers to an actual Button and Page:
button->clicked.connect( boost::bind(&Page::reload, page) );對(duì)比
或許你已經(jīng)注意到上面的例子中,無論是 Qt 的實(shí)現(xiàn)方式還是 Boost 的實(shí)現(xiàn)方式,除了必須的 Button 和 Page 兩個(gè)類之外,都不需要額外的類。兩種實(shí)現(xiàn)都解決了類爆炸的問題。下面讓我們對(duì)照著來看一下我們前面的分析。現(xiàn)在我們有:
兩個(gè)不同的術(shù)語以及各自的動(dòng)作:信號(hào)和槽;
在一個(gè)地方(信號(hào))可以連接零個(gè)或者多個(gè)回調(diào)函數(shù)(槽),同時(shí)也是多對(duì)多的;
焦點(diǎn)在于連接本身,而不是提供者或者消費(fèi)者;
不需要手工為了一個(gè)連接創(chuàng)建新的類;
連接仍舊是類型安全的。
這五點(diǎn)是信號(hào)槽系統(tǒng)的核心,Qt 和 boost 都擁有這些特性。下面則是二者的不同之處:
Boost.Signals
Qt Signals 和 Slots
一個(gè)信號(hào)就是一個(gè)對(duì)象 信號(hào)是具有名字的成員函數(shù)的簽名
發(fā)出信號(hào)類似于函數(shù)調(diào)用 發(fā)出信號(hào)類似于函數(shù)調(diào)用,Qt 提供了一個(gè) emit 關(guān)鍵字來完成這個(gè)操作
信號(hào)可以是全局的、局部的或者是成員對(duì)象 信號(hào)只能是成員函數(shù)
任何能夠訪問到信號(hào)對(duì)象的代碼都可以發(fā)出信號(hào) 只有信號(hào)的擁有者才能發(fā)出信號(hào)
槽是任何可被調(diào)用的函數(shù)或者函數(shù)對(duì)象 槽是經(jīng)過特別設(shè)計(jì)的成員函數(shù)
可以有返回值,返回值可以在多個(gè)槽中使用 沒有返回值
同步的 同步的或者隊(duì)列的
非線程安全 線程安全,可以跨線程使用
當(dāng)且僅當(dāng)槽是可追蹤的時(shí)候,槽被銷毀時(shí),連接自動(dòng)斷開 槽被銷毀時(shí),連接都會(huì)自動(dòng)斷開(因?yàn)樗胁鄱际强勺粉櫟模?
類型安全(編譯器檢查) 類型安全(運(yùn)行期檢查)
參數(shù)列表必須完全一致 槽可以忽略信號(hào)中多余的參數(shù)
信號(hào)、槽可以是模板 信號(hào)、槽不能是模板
C++ 直接實(shí)現(xiàn) 通過由 moc 生成的元對(duì)象實(shí)現(xiàn)(moc 以及元對(duì)象系統(tǒng)都是 C++ 直接實(shí)現(xiàn)的)
沒有內(nèi)省機(jī)制 可以通過內(nèi)省發(fā)現(xiàn)
可以通過元對(duì)象調(diào)用
連接可以從資源文件中自動(dòng)推斷出
最重要的是,Qt 的信號(hào)槽機(jī)制已經(jīng)深深地植入到框架之中,成為不可分割的一部分。它們可以使用 Qt 專門的開發(fā)工具,例如 QtCreator,通過拖拽的方式很輕松的創(chuàng)建、刪除、修改。它們甚至可以通過動(dòng)態(tài)加載資源文件,由特定命名的對(duì)象自動(dòng)動(dòng)態(tài)生成。這些都是 boost 作為一個(gè)通用庫所不可能提供的。