日本好好热aⅴ|国产99视频精品免费观看|日本成人aV在线|久热香蕉国产在线

  • <cite id="ikgdy"><table id="ikgdy"></table></cite>
    1. 西西軟件園多重安全檢測(cè)下載網(wǎng)站、值得信賴的軟件下載站!
      軟件
      軟件
      文章
      搜索

      首頁(yè)編程開(kāi)發(fā)其它知識(shí) → 什么是信號(hào)槽?深入理解信號(hào)槽

      什么是信號(hào)槽?深入理解信號(hào)槽

      相關(guān)軟件相關(guān)文章發(fā)表評(píng)論 來(lái)源:豆子空間時(shí)間:2010/11/19 11:47:50字體大。A-A+

      作者:FinderCheng點(diǎn)擊:480次評(píng)論:0次標(biāo)簽: 信號(hào)槽 Qt信號(hào) Boost

      Game Booster(優(yōu)化電腦游戲性能)v2.2 高級(jí)注冊(cè)版
      • 類型:游戲其他大小:6.6M語(yǔ)言:多國(guó)語(yǔ)言[中文] 評(píng)分:3.7
      • 標(biāo)簽:
      立即下載
      這篇文章來(lái)自于 A Deeper Look at Signals and Slots,Scott Collins 2005.12.19。需要說(shuō)明的是,我們這里所說(shuō)的“信號(hào)槽”不僅僅是指 Qt 庫(kù)里面的信號(hào)槽,而是站在一個(gè)全局的高度,從系統(tǒng)的角度來(lái)理解信號(hào)槽。所以在這篇文章中,Qt 信號(hào)槽僅僅作為一種實(shí)現(xiàn)來(lái)介紹,我們還將介紹另外一種信號(hào)槽的實(shí)現(xiàn)——boost::signal。因此,當(dāng)你在文章中看到一些信號(hào)的名字時(shí),或許僅僅是為了描述方便而杜撰的,實(shí)際并沒(méi)有這個(gè)信號(hào)。

      什么是信號(hào)槽?

      這個(gè)問(wèn)題我們可以從兩個(gè)角度來(lái)回答,一個(gè)簡(jiǎn)短一些,另外一個(gè)則長(zhǎng)些。

      讓我們先用最簡(jiǎn)潔的語(yǔ)言來(lái)回答這個(gè)問(wèn)題——什么是信號(hào)槽?

      信號(hào)槽是觀察者模式的一種實(shí)現(xiàn),或者說(shuō)是一種升華;
      一個(gè)信號(hào)就是一個(gè)能夠被觀察的事件,或者至少是事件已經(jīng)發(fā)生的一種通知;
      一個(gè)槽就是一個(gè)觀察者,通常就是在被觀察的對(duì)象發(fā)生改變的時(shí)候——也可以說(shuō)是信號(hào)發(fā)出的時(shí)候——被調(diào)用的函數(shù);
      你可以將信號(hào)和槽連接起來(lái),形成一種觀察者-被觀察者的關(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)聽(tīng)多個(gè)信號(hào)。

      信號(hào)可以有附加信息。例如,窗口關(guān)閉的時(shí)候可能發(fā)出 windowClosing 信號(hào),而這個(gè)信號(hào)就可以包含著窗口的句柄,用來(lái)表明究竟是哪個(gè)窗口發(fā)出這個(gè)信號(hào);一個(gè)滑塊在滑動(dòng)時(shí)可能發(fā)出一個(gè)信號(hào),而這個(gè)信號(hào)包含滑塊的具體位置,或者新的值等等。我們可以把信號(hào)槽理解成函數(shù)簽名。信號(hào)只能同具有相同簽名的槽連接起來(lái)。你可以把信號(hào)看成是底層事件的一個(gè)形象的名字。比如這個(gè) windowClosing 信號(hào),我們就知道這是窗口關(guān)閉事件發(fā)生時(shí)會(huì)發(fā)出的。

      信號(hào)槽實(shí)際是與語(yǔ)言無(wú)關(guān)的,有很多方法都可以實(shí)現(xiàn)信號(hào)槽,不同的實(shí)現(xiàn)機(jī)制會(huì)導(dǎo)致信號(hào)槽差別很大。信號(hào)槽這一術(shù)語(yǔ)最初來(lái)自 Trolltech 公司的 Qt 庫(kù)(現(xiàn)在已經(jīng)被 Nokia 收購(gòu))。1994年,Qt 的第一個(gè)版本發(fā)布,為我們帶來(lái)了信號(hào)槽的概念。這一概念立刻引起計(jì)算機(jī)科學(xué)界的注意,提出了多種不同的實(shí)現(xiàn)。如今,信號(hào)槽依然是 Qt 庫(kù)的核心之一,其他許多庫(kù)也提供了類似的實(shí)現(xiàn),甚至出現(xiàn)了一些專門(mén)提供這一機(jī)制的工具庫(kù)。

      簡(jiǎn)單了解信號(hào)槽之后,我們?cè)賮?lái)從另外一個(gè)角度回答這個(gè)問(wèn)題:什么是信號(hào)槽?它們從何而來(lái)?

      前面我們已經(jīng)了解了信號(hào)槽相關(guān)的概念。下面我們將從更細(xì)致的角度來(lái)探討,信號(hào)槽機(jī)制是怎樣一步步發(fā)展的,以及怎樣在你自己的代碼中使用它們。

      程序設(shè)計(jì)中很重要的一部分是組件交互:系統(tǒng)的一部分需要告訴另一部分去完成一些操作。讓我們從一個(gè)簡(jiǎn)單的例子開(kāi)始:

      // 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
      };換句話說(shuō),Page 類知道如何重新載入頁(yè)面(reload),Button 有一個(gè)動(dòng)作是點(diǎn)擊(click)。假設(shè)我們有一個(gè)函數(shù)返回當(dāng)前頁(yè)面 currentPage(),那么,當(dāng) button 被點(diǎn)擊的時(shí)候,當(dāng)前頁(yè)面應(yīng)該被重新載入。

      // C++ --- making the connection directly
      void Button::clicked()
      {
      currentPage()->reload(); // Buttons know exactly what to do when clicked
      }這看起來(lái)并不很好。因?yàn)?Button 這個(gè)類名似乎暗示了這是一個(gè)可重用的類,但是這個(gè)類的點(diǎn)擊操作卻同 Page 緊緊地耦合在一起了。這使得只要 button 一被點(diǎn)擊,必定調(diào)用 currentPage() 的 reload() 函數(shù)。這根本不能被重用,或許把它改名叫 PageReloadButton 更好一些。

      實(shí)際上,不得不說(shuō),這確實(shí)是一種實(shí)現(xiàn)方式。如果 Button::click() 這個(gè)函數(shù)是 virtual 的,那么你完全可以寫(xiě)一個(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)

      讓我們停下來(lái),回想一下在只有 C 的時(shí)代,我們?cè)撊绾谓鉀Q這個(gè)問(wèn)題。如果只有 C,就不存在 virtual 這種東西。重用有很多種方式,但是由于沒(méi)有了類的幫助,我們采用另外的解決方案:函數(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_);
      }這就是通常所說(shuō)的“回調(diào)”。buttonClicked() 函數(shù)在編譯期并不知道要調(diào)用哪一個(gè)函數(shù)。被調(diào)用的函數(shù)是在運(yùn)行期傳進(jìn)來(lái)的。這樣,我們的 Button 就可以被重用了,因?yàn)槲覀兛梢栽谶\(yùn)行時(shí)將不同的函數(shù)指針傳遞進(jìn)來(lái),從而獲得不同的點(diǎn)擊操作。

      增加類型安全

      對(duì)于 C++ 或者 Java 程序員來(lái)說(shuō),總是不喜歡這么做。因?yàn)檫@不是類型安全的(注意 url 有一步強(qiáng)制類型轉(zhuǎn)換)。

      我們?yōu)槭裁葱枰愋桶踩兀恳粋(gè)對(duì)象的類型其實(shí)暗示了你將如何使用這個(gè)對(duì)象。有了明確的對(duì)象類型,你就可以讓編譯器幫助你檢查你的代碼是不是被正確的使用了,如同你畫(huà)了一個(gè)邊界,告訴編譯器說(shuō),如果有人越界,就要報(bào)錯(cuò)。然而,如果沒(méi)有類型安全,你就丟失了這種優(yōu)勢(shì),編譯器也就不能幫助你完成這種維護(hù)。這就如同你開(kāi)車(chē)一樣。只要你的速度足夠,你就可以讓你的汽車(chē)飛起來(lái),但是,一般來(lái)說(shuō),這種速度就會(huì)提醒你,這太不安全了。同時(shí)還會(huì)有一些裝置,比如雷達(dá)之類,也會(huì)時(shí)時(shí)幫你檢查這種情況。這就如同編譯器幫我們做的那樣,是我們出浴一種安全使用的范圍內(nèi)。

      回過(guò)來(lá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)可以很方便的重用了,并且也是類型安全的,再也沒(méi)有了強(qiáng)制類型轉(zhuǎn)換。這種實(shí)現(xiàn)已經(jīng)可以解決系統(tǒng)中遇到的絕大部分問(wèn)題了。似乎現(xiàn)在的解決方案同前面的類似,都是繼承了一個(gè)類。只不過(guò)現(xiàn)在我們對(duì)動(dòng)作進(jìn)行了抽象,而之前是對(duì) Button 進(jìn)行的抽象。這很像前面 C 的實(shí)現(xiàn),我們將不同的動(dòng)作和 Button 關(guān)聯(lián)起來(lái),F(xiàn)在,我們一步步找到一種比較令人滿意的方法。

      多對(duì)多

      下一個(gè)問(wèn)題是,我們能夠在點(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)。不要覺(jué)得這種實(shí)現(xiàn)看上去沒(méi)什么水平,實(shí)際上我們發(fā)現(xiàn)這就是一種相當(dāng)簡(jiǎn)潔的方法。同時(shí),不要糾結(jié)于我們代碼中的 std:: 和 boost:: 這些命名空間,你完全可以用另外的類,強(qiáng)調(diào)一下,這只是一種可能的實(shí)現(xiàn),F(xiàn)在,我們的一個(gè)動(dòng)作可以連接多個(gè) button 了,當(dāng)然,也可以是別的 Action 的使用者,F(xiàn)在,我們有了一個(gè)多對(duì)多的機(jī)制。通過(guò)將 AbstractAction* 替換成 boost::shared_ptr<AbstractAction>,可以解決 AbstractAction 的歸屬問(wèn)題,同時(shí)保持原有的多對(duì)多的關(guān)系。

      這會(huì)有很多的類!

      如果你在實(shí)際項(xiàng)目中使用上面的機(jī)制,很多就會(huì)發(fā)現(xiàn),我們必須為每一個(gè) action 定義一個(gè)類,這將不可避免地引起類爆炸。至今為止,我們前面所說(shuō)的所有實(shí)現(xiàn)都存在這個(gè)問(wèn)題。不過(guò),我們之后將著重討論這個(gè)問(wèn)題,現(xiàn)在先不要糾結(jié)在這里啦!

      特化!特化!

      當(dāng)我們開(kāi)始工作的時(shí)候,我們通過(guò)將每一個(gè) button 賦予不同的 action,實(shí)現(xiàn) Button 類的重用。這實(shí)際是一種特化。然而,我們的問(wèn)題是,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)行封裝,其他別無(wú)用處。這在 C++ 里面還是比較普遍的,很多時(shí)候我們用 ++ 的特性重新封裝函數(shù),讓類的行為看起來(lái)就像函數(shù)一樣。例如,我們重載 operator() 運(yùn)算符,就可以讓類看起來(lái)很像一個(gè)函數(shù):

      class AbstractAction
      {
      public:
      virtual void operator()() = 0;
      };

      // using an action (given AbstractAction& action)
      action();這樣,我們的類看起來(lái)很像函數(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()();看起來(lái)很麻煩,值得這樣做嗎?

      下面我們來(lái)試著解釋一下信號(hào)和槽的目的?瓷先,重寫(xiě) operator() 運(yùn)算符有些過(guò)分,并不值得我們?nèi)ミ@么做。但是,要知道,在某些問(wèn)題上,你提供的可用的解決方案越多,越有利于我們編寫(xiě)更簡(jiǎn)潔的代碼。通過(guò)對(duì)一些類進(jìn)行規(guī)范,就像我們要讓函數(shù)對(duì)象看起來(lái)更像函數(shù),我們可以讓它們?cè)谀承┉h(huán)境下更適合重用。在使用模板編程,或者是 Boost.Function,bind 或者是模板元編程的情形下,這一點(diǎn)尤為重要。

      這是對(duì)無(wú)需更多特化建立信號(hào)槽連接重要性的部分回答。模板就提供了這樣一種機(jī)制,讓添加了特化參數(shù)的代碼并不那么難地被特化,正如我們的函數(shù)對(duì)象那樣。而模板的特化對(duì)于使用者而言是透明的。

      松耦合

      現(xiàn)在,讓我們回顧一下我們之前的種種做法。

      我們執(zhí)著地尋求一種能夠在同一個(gè)地方調(diào)用不同函數(shù)的方法,這實(shí)際上是 C++ 內(nèi)置的功能之一,通過(guò) virtual 關(guān)鍵字,當(dāng)然,我們也可以使用函數(shù)指針實(shí)現(xiàn)。當(dāng)我們需要調(diào)用的函數(shù)沒(méi)有一個(gè)合適的簽名,我們將它包裝成一個(gè)類。我們已經(jīng)演示了如何在同一地方調(diào)用多個(gè)函數(shù),至少我們知道有這么一種方法(但這并不是在編譯期完成的)。我們實(shí)現(xiàn)了讓“信號(hào)發(fā)送”能夠被若干個(gè)不同的“槽”監(jiān)聽(tīng)。

      不過(guò),我們的系統(tǒng)的確沒(méi)有什么非常與眾不同的地方。我們來(lái)仔細(xì)審核一下我們的系統(tǒng),它真正不同的是:

      定義了兩個(gè)不同的術(shù)語(yǔ):“信號(hào)”和“槽”;
      在一個(gè)調(diào)用點(diǎn)(信號(hào))與零個(gè)或者多個(gè)回調(diào)(槽)相連;
      連接的焦點(diǎn)從提供者處移開(kāi),更多地轉(zhuǎn)向消費(fèi)者(也就是說(shuō),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ù)描述連接呢?這么一來(lái),我們就有了松耦合的類,從而提高二者的可重用性。

      新的連接模式

      什么樣的連接模式才算是非代碼描述呢?假如僅僅只有一種信號(hào)槽的簽名,例如 void (*signature)(),這并不能實(shí)現(xiàn)。使用散列表,將信號(hào)的名字映射到匹配的連接函數(shù),將槽的名字映射到匹配的函數(shù)指針,這樣的一對(duì)字符串即可建立一個(gè)連接。

      然而,這種實(shí)現(xiàn)其實(shí)包含一些“握手”協(xié)議。我們的確希望具有多種信號(hào)槽的簽名。在信號(hào)槽的簡(jiǎn)短回答中我們提到,信號(hào)可以攜帶附加信息。這要求信號(hào)具有參數(shù)。我們并沒(méi)有處理成員函數(shù)與非成員函數(shù)的不同,這又是一種潛在的函數(shù)簽名的不同。我們還沒(méi)有決定,我們是直接將信號(hào)連接到槽函數(shù)上,還是連接到一個(gè)包裝器上。如果是包裝器,這個(gè)包裝器需要已經(jīng)存在呢,還是我們?cè)谛枰獣r(shí)自動(dòng)創(chuàng)建呢?雖然底層思想很簡(jiǎn)單,但是,真正的實(shí)現(xiàn)還需要很好的努力才行。似乎通過(guò)類名能夠創(chuàng)建對(duì)象是一種不錯(cuò)的想法,這取決于你的實(shí)現(xiàn)方式,有時(shí)候甚至取決于你有沒(méi)有能力做出這種實(shí)現(xiàn)。將信號(hào)和槽放入散列表需要一種注冊(cè)機(jī)制。一旦有了這么一種系統(tǒng),前面所說(shuō)的“有太多類了”的問(wèn)題就得以解決了。你所需要做的就是維護(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ù)可用,這就是一種腳本能力。如果你需要上面所說(shuō)的種種特性,那么,完成這么一套系統(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è)需要這些特性的庫(kù)而言,完整地實(shí)現(xiàn)出來(lái)就是一個(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)的最后一部分來(lái)討論這個(gè)問(wèn)題。

        相關(guān)評(píng)論

        閱讀本文后您有什么感想? 已有人給出評(píng)價(jià)!

        • 8 喜歡喜歡
        • 3 頂
        • 1 難過(guò)難過(guò)
        • 5 囧
        • 3 圍觀圍觀
        • 2 無(wú)聊無(wú)聊

        熱門(mén)評(píng)論

        最新評(píng)論

        發(fā)表評(píng)論 查看所有評(píng)論(0)

        昵稱:
        表情: 高興 可 汗 我不要 害羞 好 下下下 送花 屎 親親
        字?jǐn)?shù): 0/500 (您的評(píng)論需要經(jīng)過(guò)審核才能顯示)