• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            flipcode
            仿函數、綁定、橋接、委托相關討論:
            以下隨便討論下,沒突出的中心論點,個中理論只代表我個人觀點,難免有錯:),歡迎指正。
            一。需求:
            在事件處理常常會碰到這樣的情況:
            1。接口分離。即invokers(調用者)與(receivers)接收者分離。
            2。時間分離。

            比如說:UI相關元素(按鈕、菜單等)就是一個invokers。
            receivers則是響應命令的對象(如對話框或應用程序本身)。
            這需要我們要先將UI相關元素的事件響應的接收者在初始化時先保存起來。
            待后用戶按下按鈕等再觸發(即invokers通過調用對應先前保存的receivers來執行命令)
            嗯,在delphi、java、vcl、.net中有相關的實現。而vc則需要自己來弄。

            二。仿函數的實現:
            在說仿函數前先說說我們應該怎么保存這些操作相關函數的呢?
            // 一般的函數我們可以這么存:
            void (*fun)() = Test;
            (*fun)();
            // 而類成員函數可以這么做:
            void (CTest::*mfn)(); // 或用 typedef void (CTest::*MFN_TEST)(); MFN_TEST mfn;
            mfn = CTest::Test;
            CTest a, *p=new CTest;
            (a.*mfn)(); // 調用方法1
            (p->*mfn)(); // 調用方法2
            如上所述可見為了處理前面所述的事件響應情況,我們通常會用回調函數,
            就是把類成員函數定義為靜態函數,在初始時保存函數地址(與一般函數處理類同)及對應的對象指針,
            在事件觸發時調用對應的靜態函數,而該函數中在把指針強制轉化為對應類型對象地址,
            得以操縱該對象的成員變量(嗯,理論上跟成員函數的實現差不多,成員函數會由編譯器安插一個
            this指針作為第1個參數傳給函數,以便可以操作該this對象的成員)。

            回調函數應用的具體代碼如下:
            1). 回調接口(靜態函數法):
            //======================================================
            #include "stdafx.h"
            #include

            typedef void(*KEY_RESPOND)(void* /*,param*/);
            struct CListener
            {
            void* pThis;
            KEY_RESPOND pfn;
            CListener() : pThis(0), pfn(0){}
            };
            class CInput
            {
            std::list m_listListener;
            public:
            void AddListener( CListener* pListener ){
            m_listListener.push_back( pListener );
            }
            void RemoveListerner( CListener* pListener ){
            std::list::iterator iter;
            for( iter = m_listListener.begin(); iter!= m_listListener.end();iter++ ){
            if( pListener == (*iter) ){
            m_listListener.erase( iter ); break;
            }
            }
            }
            void HitOneKey(){
            std::list::iterator iter;
            for( iter = m_listListener.begin(); iter!= m_listListener.end();iter++ ){
            if( (*iter)->pfn && (*iter)->pThis ){
            (*(*iter)->pfn)( (*iter)->pThis );
            }
            }
            }
            void clearListener(){
            m_listListener.clear();
            }
            };
            class CUI
            {
            public:
            static void InputProc(void* pThis/*,param*/){
            __asm int 3 // 下個斷點測試下(某些編譯器不能這么寫,vc可以)
            }
            };

            CUI ui;
            CInput input;
            int _tmain(int argc, _TCHAR* argv[])
            {
            // 初始:
            CListener* pListener = new CListener;
            pListener->pfn = &CUI::InputProc;
            pListener->pThis = &ui;
            // 觸發:
            input.AddListener( pListener ); // input即為invokers(調用者,但叫觸發者好點)
            input.HitOneKey(); // 某處事件觸發,內部呼叫receivers(這里是原先保存的CUI對象)來真正處理該事件(InputProc(...)方法)。
            // 清除
            input.clearListener();
            if( pListener )
            {
            delete pListener;
            pListener = NULL;
            }
            return 0;
            }
            //======================================================

            // 第2種方法: 回調類(虛函數多態法):
            //======================================================
            class IWillBack
            {
            public:
            virtual void InputProc(/*參數略...*/){}
            };
            class CInput
            {
            public:
            void RegisterListener(IWillBack* pListener){
            m_pListener = pListener; // 這里用list存起來才好,這里只作測試
            }
            void OnInputEvent(){
            m_pListener->InputProc(/*參數略...*/);
            }

            private:
            IWillBack* m_pListener;
            };
            class CUI : public IWillBack
            {
            public:
            void InputProc(/*參數略...*/){ /*..實際處理代碼..*/}
            private:
            };

            int _tmain(int argc, _TCHAR* argv[])
            {
            CInput aa;
            CUI bb;
            aa.RegisterListener(&bb);
            aa.OnInputEvent();
            return 0;
            }
            //======================================================

            但是第1種靜態函數用法是不直觀的,第2種需要派生增加了之間的聯系,而為了方便我們通常會將成員函數指針轉化為函數對象來處理,即仿函數(一般是指重載了()操作符的類)來實現。

            類似于這樣的操作,stl提供了mem_fun、mem_fun_ref、binder1st、binder2nd簡單操作。
            但stl的方法相對比較原始而受限制,比如說std::mem_fun需要成員函數有返回值,
            std::mem_fun最多只能支持成員函數有一個參數等,
            下面來看std:mem_fun_ref不支持成員函數返回值為void的一個例子:
            //======================================================
            #include
            class CFoo
            {
            public:
            void test() // 只有將void改成別的類型才可以,如:int
            {
            return 0;
            }
            };
            void main()
            {
            CFoo t;
            std::mem_fun_ref(&CFoo::test)(t);
            }
            //======================================================
            上述代碼只有將void改成別的類型(如int)才可以,
            那么為什么不可以處理返回void的函數呢? stl的實現究竟是怎么樣的呢?
            嗯,stl簡單實現了mem_fun_ref及mem_fun,其中mem_fun_ref以引用方式處理函數所屬對象,
            而mem_fun以指針方式處理函數所屬對象。
            現在讓我們從vc的stl挖出部份代碼來看看,

            1.stl的實現:
            以mem_fun_ref為例(省略某些對說明不重要的細節,兩條虛線包括的代碼為stl類似源碼):
            //======================================================
            //----------------------------------------------------------
            namespace stl_test
            {
            // 主要實現:
            template
            class mem_fun_ref_t
            {
            public:
            mem_fun_ref_t( R (T::*_Pm)() ) : _Ptr(_Pm) {} // 構造: 保存成員函數地址
            R operator()(T& _X) const // 調用: 這里可看出mem_fun_ref以引用方式處理
            {
            return ((_X.*_Ptr)()); // 這里執行調用函數,并返回該函數所返回值
            }
            private:
            R (T::*_Ptr)(); // 指向成員函數地址的指針
            };
            // 這里只是利用函數的參數推導來自動獲取型別(方便調用)
            template inline
            mem_fun_ref_t mem_fun_ref(R (T::*_Pm)())
            {
            return (mem_fun_ref_t(_Pm));
            }

            } // end of namespace test_stl
            //----------------------------------------------------------
            class CFoo
            {
            public:
            int test1(){
            __asm int 3
            return 0;
            }
            void test2(){
            __asm int 3
            }
            };
            int APIENTRY WinMain(HINSTANCE hInstance,
            HINSTANCE hPrevInstance,
            LPSTR lpCmdLine,
            int nCmdShow)
            {
            CFoo t;
            stl_test::mem_fun_ref( &CFoo::test1 ) (t);
            return 0;
            }
            //======================================================
            /////////////////////////////////////////////////////////////////
            從源碼"return ((_X.*_Ptr)()); " 可以看到stl直接返回該函數所返回值。
            所以函數沒有返回值(即為void時)的話編譯器就會報錯。好,那么如果我們在
            這里只是直接執行函數而不用return返回的話編譯器應該可以通過了。

            嗯,boost中正是這么處理的。(btw.為了更為通用,boost對stl原有仿函數及綁定作了大量的改進工作)。
            但是具體應該怎么區分有沒有返回值呢?這個也容易,我們只需用到模板的偏特性就可
            以做到。下面就看看boost的實現(btw.boost有兩種版本,我用的是兼容版本,代碼難看)

            2. boost的實現(這里我把boost的一大堆宏(真@$@#@#難看,loki在這方面來得比較清爽)去掉了):
            /////////////////////////////////////////////////////////////////
            // notreturn.cpp : Defines the entry point for the application.
            //

            #include "stdafx.h"
            //------------------------------------
            namespace boost_test
            {
            template // 有返回值時會調用這個
            struct mf
            {
            template
            class inner_mf0
            {
            R (T::*_Ptr)();
            public:
            inner_mf0(R (T::*f)()) : _Ptr(f) {}
            R operator()(T& X) const
            {
            return ((X.*_Ptr)());
            }
            };
            };
            template<> // 沒有反回值時會調用這個
            struct mf // 偏特化
            {
            template
            class inner_mf0
            {
            R (T::*_Ptr)();
            public:
            inner_mf0(R (T::*f)()) : _Ptr(f) {}
            R operator()(T& X) const
            {
            ((X.*_Ptr)());
            }
            };
            };
            // 創建一派生類,派生于上述基類
            template
            struct mf0 : public mf::inner_mf0
            {
            typedef R(T::*F)();
            explicit mf0(F f) : mf::inner_mf0(f) {}
            };
            // 通過函數的參數推導自動獲取類型
            template
            mf0 mem_fn( R(T::*f)() )
            {
            return mf0(f);
            }
            } // namespace boost_test
            //------------------------------------

            class CFoo
            {
            public:
            int test1(){ return 0; }
            void test2(){}
            };
            int APIENTRY WinMain(HINSTANCE hInstance,
            HINSTANCE hPrevInstance,
            LPSTR lpCmdLine,
            int nCmdShow)
            {
            CFoo t;
            boost_test::mem_fn( &CFoo::test1 ) (t);
            return 0;
            }
            /////////////////////////////////////////////////////////////////

            從上述代碼可以看到偏特性幫助我們解決了返回值為void的情況。但是手寫了兩份
            基本相同的代碼。。。
            另外處理參數個數的情況也很容易,只要分別實現不同參數的各個模板類就可以了,
            boost最多只能支持成員函數有8個參數,因為它內部實現了8份這樣不同參數模板類。
            其實的處理方法都是一模一樣的,可是由于語言的限制我們還是沒有辦法不一一實現
            不同參數的類:。在loki中參數可以用TList實現任意的參數,但是在實現還是得老
            老實實的每份手寫一份(loki實現了15份可以支持15個參數)。
            這真讓人郁悶。。。不過沒辦法。

            說完來仿函數,下面開始說說有關綁定,stl、boost、loki的綁定的意思是
            對某物實體的“綁定”,通俗來說是指對函數、構造函數、仿函數等與其對應的某個參數的綁定,
            以便在調用時不用再次輸入此參數(因為某些時候參數是固定的,比如說綁定一個內部存有
            成員函數地址的仿函數和它對應的對象地址在一起)。
            以下是stl的bind用法:
            //================================
            #include "stdafx.h"
            #include // stl
            #include // boost

            struct CFoo {
            int test(int){
            return 0;
            }
            };
            void main()
            {
            boost::function1 f; // 這里用了boost
            CFoo obj;
            f = std::bind1st(std::mem_fun(&CFoo::test), &obj);
            f(5);
            }
            //================================
            loki中的BindFirst比較類似于stl的binder
            (binder1st,binder2nd),但是它是通用的,可以通過嵌套實現任意多個參數綁定:
            //================================
            void f()
            {
            Functor cmd1(something);
            Functor cmd2(BindFirst(cmd1, 10));
            cmd2(20);
            Functor cmd3(BindFirst(cmd2, 30));
            cmd3();
            }

            而boost中的實現是以占位符來表現,具體如何實現,下回繼續討論(嗯,
            boost代碼的宏太多了,這部份還是等有空再補全了,現在我們來看看如何實現一個委托類)

            三。委托類的實現:
            1. 橋接模式:
            設計模式告訴我們可以使用橋接模式(Bridge Pattern)減少對象之間的 耦合度,橋接模式如下:
            Invoker <>------------------->* Interface
            ^
            |
            Receiver
            上圖的Invoker表示事件觸發者,Receiver表示事件處理者,符號類似于<c++大規模編程。。>一書所描述(注:這里符號對位可能出錯:變成左對齊了),
            其中<>------------------->表示Invoker 內含(擁用)Interface(即Invoker 有Interface的變量或指針并負責Interface的釋放),
            而*號表示可有多個。
            ^
            |  號則表示繼承于(Receiver繼承于Interface)。

            好,我們先來分析前面在" 第2種方法: 回調類(虛函數多態法):"的實現思想(請回到前面看看代碼),
            它其實就是一個橋接模式,如下(括號內對應前面所實現的類):
            Invoker(CInput) <>--------------->* Interface(IWillBack)
            ^
            |
            Receiver(CUI)
            對照我們前面實現的代碼可以發現此種實現的橋接的缺點是:每一個想要
            注冊一方法到Invoker中以便Invoker在事件觸發時調用的類(如Receiver)都要派生自Interface。
            有沒有更好的辦法再次減少這種耦合度呢?這正是下面我們要討
            論的下一種設計模式:
            2. 委托與事件:
            委托的處理設計如下:
            Invoker <>--------------------->* Interface
            ^
            |
            Implementation -----------------> Receiver
            即在原橋接模式下再加一層間接性:Implementation 。其中
            Implementation與Receiver之間的----------------->表示Implementation引用了Receiver一些服務,
            即Implementation用到了Receiver某些東西(如函數或數據)。嗯,這些解釋不知是否適當,希望不會誤導。。。
            好,一開始可能我們會這么設計:
            //======================================================================================
            class handle {};
            template
            class Implementation : public handle
            {
            T* m_pThis;
            public:
            Implementation ( T* pThis ) : m_pThis(pThis) {}
            template
            void execute( void (T::*mfn)(T1), T1 arg ) { (m_pThis->*mfn)( arg ); }

            };
            struct Receive {
            void Proc(int) {
            __asm int 3
            }
            };
            Receive a;
            void Invoker(){
            Implementation test = Implementation (&a);
            test.execute( Receive::Proc, 10 ); // 當事件發生時調用
            };
            int _tmain(int argc, _TCHAR* argv[])
            {
            Invoker();
            }
            //======================================================================================
            但是Invoker知道了太多Receive的信息,況且我們想讓觸發者Invoker作成一個類。
            一個改進的版本如下:
            //-------------------------------------------------------------
            // signal slot system
            // 注: 該法我是看了"落木隨風"的"委托、信號和消息反饋的模板實現技術",
            // 代碼作了部份添加。在這里非常的感謝他!
            // 他的博客:http://blogs.gcomputing.com/rocwood/archives/000154.html
            //-------------------------------------------------------------
            // Delegation.cpp : Defines the entry point for the console application.
            //
            #include

            #ifndef K_DELEGATE_H
            #define K_DELEGATE_H

            namespace kUTIL
            {
            // 1. 橋接類(純虛類):
            // 為什么叫作橋接?
            // 因為通過它的虛函數方法可以調用到對應的正確的不同派生實例(指后面
            // 提到的委托人)所改寫的虛函數方法(這是委托人用來完成委托任務的方法)
            struct kDelegationInterface
            {
            virtual ~kDelegationInterface() {};
            virtual void Execute() = 0;
            };
            // 2. 委托類(派生于橋接類,這里我叫為”委托人“)
            // 為什么叫委托?
            // 因為調用者把“通知”的工作委托給它來負責處理。
            // 一個“委托人”保存了: a.”接收者“(對象指針m_pThis) 及 b.“要作的事”(方法指針m_mfn),
            // 以便調用者發出信號彈(后面提到,信號彈有一個作橋接用的純虛類的指針指向相應的委托人)
            // 告知此信號對應的委托人來完成它被委托的工作:即讓“接收者”(m_pThis)作”要作的事“(m_mfn)。
            template
            struct kDelegationImpl : public kDelegationInterface
            {
            typedef void ( T::* MFN )();
            kDelegationImpl( T* pthis, MFN mfn ) : m_pThis( pthis ), m_mfn( mfn ) {
            }
            virtual void Execute() {
            if( m_pThis ) {
            ( m_pThis->*m_mfn )();
            }
            }
            T* m_pThis;
            MFN m_mfn;
            };

            // 3. 信號彈(實現為仿函數來調用統一的虛函數接口):
            // 為什么叫信號?
            // 因為當"信號彈"發射時(調用信號的操作符"()")
            // 它會通知所指向的"委托人"事件發生了(調用純虛類指針的m_DI->Execute()方法)。
            // 一個信號保存了一個指向對應”委托人“的橋接類(純虛類)指針。
            struct kSignal0
            {
            kDelegationInterface* m_DI; // 純虛類的指針
            kSignal0() : m_DI(0) {}
            // 1. 純虛類的m_DI指針可以指向不同的派生實例:
            template
            void ConnectSlot(T* recv, void (T::* mfn)()) {
            DisConnect();
            m_DI = new kDelegationImpl( recv, mfn );
            int test = 0;
            }
            void DisConnect() {
            if( m_DI ) { delete m_DI; m_DI = NULL; }
            }
            // 2. 用統一的純虛類指針調用不同派生類改寫的虛函數
            void operator() () {
            if( m_DI ) {
            m_DI->Execute();
            }
            }
            };

            // 下面是兩個為方便使用的函數:
            template
            void kConnect( kSignal0& sgn, T* pObj, void(T::*fn)())
            {
            sgn.ConnectSlot( pObj, fn );
            int i = 0;
            }
            inline void kDisConnect( kSignal0& sgn )
            {
            sgn.DisConnect();
            }
            } // end of namespace kUTIL
            #endif //#ifndef K_DELEGATE_H

            //----------------------------------------------------------------------------
            // 一個使用實例:
            class kButton {
            public:
            kUTIL::kSignal0 sgnMouseBtnUp;
            void OnMouseButtonUp() { sgnMouseBtnUp(); }
            };

            class kDialog {
            kButton btn;
            public:
            kDialog() {
            kUTIL::kConnect( btn.sgnMouseBtnUp, this, &kDialog::DoWork ); // vc6下這里kDialog::DoWork的前面一定要可加"&"號
            }
            void DoWork() {
            __asm int 3
            }
            void TestMouseHit() { btn.OnMouseButtonUp(); }
            };

            int main(int argc, char* argv[])
            {
            kDialog dlg;
            kButton btn;
            kUTIL::kConnect( btn.sgnMouseBtnUp, &dlg, kDialog::DoWork ); // vc6下這里kDialog::DoWork的前面可加/不加"&"號

            // 測試一:
            btn.OnMouseButtonUp();

            // 測試二:
            dlg.TestMouseHit();
            return 0;
            }

            // 委托實例總結:
            // 下面我們來具體說明”當某事發生時,調用者發射信號彈通知對應的接收者作相應處理“
            // 1. "調用者" 擁有各種信號彈。
            // 2. 初始時,我們把信號彈與對應的委托人聯系起來,并讓委托人記錄在信號觸發時應該通知的"接收人"和"接收人該作的事"。
            // a. 信號彈保存了橋(純虛類)指針,指針指向通過其模板函數ConnectSlot方法來找出(產生的)委托人(委托實例)。
            // b. 委托人(委托實例)在信號彈用ConnectSlot方法產生它的時候保存了函數ConnectSlot所傳入的兩個參數:
            // 即"接收者指針"及"其方法指針"。
            // 3. 當事件發生時"調用者"發射對應信號彈后,信號彈會調用其所保存的純虛類指針的虛函數方法,
            // 于是由于虛函數特性就會調用到其所指向的委托實例(委托人)所改寫的方法。
            // 5. 委托人改寫的方法中通過其所保存的”接收者指針“及其"方法指針"來呼叫"接收者"用對應的”方法指針“
            // 來處理事情。
            // 即如下流程:
            // "調用者"發射"信號彈" ---> "信號彈"通過"橋"找到對應"委托人" ---> "委托人"呼叫"接收者"作"該作的事"

            //=============================================================================
            嗯,這樣到此,一個非常方便的委托類就得以實現了!如果你還不懂的話請仔細的琢磨,如此精華(因為簡單而強大)
            不要錯過。不過上述只是部份實現,當你要支持帶參數及返回值的各種情況的話,還得自己作擴充。
            返回值的處理方法可參見前面剖述boost的mem_fn的處理方法,而帶不同參數的處理則只能一一手動
            實現,就象前面所說的那樣,這是很無奈的事情,但是目前來說沒有辦法。。。

            (待續。。。。。。,如果有時間有必要的話。。。)

            附:
            loki下載:
            http://sourceforge.net/projects/loki-lib/

            boost下載:
            http://sourceforge.net/projects/boost/

            2004.10.26更新:
            修正:
            原void connect( Signal0& sgn,T1 obj, void(T2::*fn)())改成
            void connect( Signal0& sgn,T1& obj, void(T2::*fn)())
            另外加了kDisConnect釋放內存,原來只作測試沒寫它,現在還是加上了。

            --------------------------------------------------------------------------------

            千里馬肝
            實在是太好了

            PS,陽春的東西,就會曲高合寡。 :D

            --------------------------------------------------------------------------------

            flipcode
            好久沒上網了,
            今天上來看到版主的夸獎,
            雖然是過獎了,不過還是感覺輕飄飄的。。。嘿嘿~~~, :D

            --------------------------------------------------------------------------------

            kane
            嗯,這種技巧是比較實用的。

            posts - 94, comments - 138, trackbacks - 0, articles - 94

            Copyright © RichardHe

            国产AⅤ精品一区二区三区久久| 无码精品久久一区二区三区| 精品久久久久久国产| 久久婷婷国产剧情内射白浆 | 久久精品国产99久久无毒不卡 | 久久中文字幕一区二区| 99久久精品免费观看国产| 亚洲精品国产自在久久| 国产精品久久久久jk制服| 久久久久无码专区亚洲av| 久久综合香蕉国产蜜臀AV| 久久久久九九精品影院| 九九久久99综合一区二区| 久久精品国产免费观看| 久久99精品国产99久久6| 久久av无码专区亚洲av桃花岛| 国产精品丝袜久久久久久不卡| 国产美女亚洲精品久久久综合| 国产福利电影一区二区三区,免费久久久久久久精 | 久久久久亚洲爆乳少妇无| 国产美女久久精品香蕉69| 久久精品国产欧美日韩99热| 国产精品狼人久久久久影院| 久久精品亚洲日本波多野结衣| 久久久久久久免费视频| 久久精品三级视频| 很黄很污的网站久久mimi色| 国产产无码乱码精品久久鸭| 久久久久久久女国产乱让韩| 亚洲精品国产第一综合99久久| 久久精品不卡| 武侠古典久久婷婷狼人伊人| 久久久久国产一区二区三区| 88久久精品无码一区二区毛片 | 国产精品99久久精品爆乳| 国产精品久久久久aaaa| 久久国产精品成人片免费| 色欲综合久久中文字幕网| 人妻无码αv中文字幕久久琪琪布| 伊色综合久久之综合久久| 久久久久亚洲?V成人无码|