C++實現委託,事件原理講解

摘要: 介紹了事件委託機制的需求,各類解決方案的演變,最終提出模板化的事件委託機制,並給出較詳細的進化過程和原理說明。
關鍵詞: C++,委託,委託器,事件器,模板
第一章 基礎版實現
在平時的工做中,咱們常常會遇到如下狀況
void Do(int event_id)
{
     …
}
void OnEvent(int event_id)
{
     Do(event_id);
}
下面是成員函數版本
class A
{
public:
     void Do(int event_id)
     {
         …
     }
};
class B
{
public:
     void OnEvent(int event_id)
     {
         a.Do(event_id);
     }
private:
     A    a;
};
 (這裏a或者是B的成員,或者是全局變量,或者經過OnEvent函數傳遞進來)
以上是通常狀況,當B的OnEvent還須要調用另外的函數或者其餘對象的函數時,就不得不對OnEvent函數做出改動,固然若是A的類型改變了,也要作相應改動,變成
void OnEvent(int event_id)
{
     c.Run(event_id);
}
或者
void OnEvent(int event_id)
{
     a.Do(event_id);
     c.Run(event_id);
     …
}
因爲需求的多變性,致使OnEvent函數面對不一樣的狀況有不一樣的實現,類B的複用性大大下降。咱們知道GUI是接收事件並做出處理的一個典型例子,若是按照以上方法,則每一種控件都須要被繼承,重載OnEvent函數,用以對應不一樣的事件響應,是一件很可怕的任務,
:(
第二章 多態版實現
2.1 單任務的實現
C++提供了多態機制,咱們可使用類的虛函數改善以上的問題。
(在C中可使用函數指針的方法,其本質是相同的,這個就由讀者本身發揮了)
class EventCallerBase
{
public:
     // 基類使用純虛函數,派生類必須實現
     virtual void Do(int event_id) = 0;
};
class Receiver
{
public:
     void SetEventCaller(EventCallerBase* pCaller) { m_pCaller = pCaller; }
     void OnEvent(int event_id)
     {
         if (m_pCaller)
              m_pCaller->Do(event_id);
     }
private:
     EventCallerBase* m_pCaller;
};
class EventCallerA : public EventCallerBase
{
public:
     virtual void Do(int event_id)
     {
         printf("EventCallerA do event %d.\r\n", event_id);
     }
};
void main()
{
     EventCallerA caller;
     Receiver receiver;
     receiver.SetEventCaller(&caller);
     …
     receiver.OnEvent(99);
}
輸出:EventCallerA do event 99.
2.2 多任務的實現
對於須要對多個對象調用其函數的狀況,用如下方式
EventCallerBase,EventCallerA的實現同上
class EventCallerB : public EventCallerBase
{
public:
     virtual void Do(int event_id)
     {
         printf("EventCallerB do event %d.\r\n", event_id);
     }
};
class Receiver
{
public:
     void AddEventCaller(EventCallerBase* pCaller)
     {
         if (pCaller)
              m_CallerList.push_back(pCaller);
     }
     void OnEvent(int event_id)
     {
         list<EventCallerBase*>::iterator it = m_CallerList.begin();
         while (it != m_CallerList.end())
         {
              EventCallerBase* pCaller = *it;
              if (pCaller)
                   pCaller->Do(event_id);
              ++it;
         }
     }
private:
     list<EventCallerBase*> m_CallerList;
};
void main()
{
     EventCallerA callerA; 
     EventCallerB callerB;
     Receiver receiver;
     receiver.AddEventCaller(&callerA);
     receiver.AddEventCaller(&callerB);
     …
     receiver.OnEvent(99);
}
輸出:EventCallerA do event 99.
      EventCallerB do event 99.
在以上方法中,類Receiver基本作到了重用,除了OnEvent參數類型和個數的改變,通常狀況下,當有事件發生,調用不一樣的事件處理函數時,只要繼承EventCallerBase類,實現Do函數,並在初始階段設定AddEventCaller便可。這種方法在GUI中,已經能儘量地重用發生事件部分的類和代碼,把主要工做放在實現事件響應的處理上。
2.3 對已有類的改造
這裏有個問題,若是有一個需求,好比窗口最大化,須要調用成員函數System::Maximize(),怎麼辦?類System是一個既有類,不能隨便改動,來繼承EventCallerBase。上面的方法豈不是不實用?
小小地動動腦筋,方法是有的:
class System

public:
     void Maximize(void)   { printf("Window is maximized.\r\n"); }
};
class EventCallerSystem : public EventCallerBase
{
public:
     EventCallerSystem(System* pSystem) { m_pSystem = pSystem; }
     virtual void Do(int event_id)
     {
         if (m_pSystem)
              m_pSystem->Maximize()
     }
private:
     System* m_pSystem;
};
void main()
{
     System system;
     EventCallerSystem callerSystem(&system);
     Receiver receiver;
     receiver.AddEventCaller(&callerSystem);
     …
     receiver.OnEvent(99);
}
輸出:Window is maximized.
解決了問題,還留了一個小尾巴,就是要多實現一個EventCallerSystem類。
有沒有辦法把這個小尾巴也一併解決掉呢,這就到了這篇文章的主題――C++中的事件委託機制,此次咱們用到了C++的另外一個特性---模板。
第三章 事件委託版實現
3.1 函數指針的使用
咱們首先複習一下函數指針和成員函數指針。
3.1.1 函數指針
在C和C++語言中,一個命名爲my_func_ptr的函數指針指向以一個int和一個char*爲參數的函數,這個函數返回一個浮點值,聲明以下:
float (*my_func_ptr)(int, char *);
爲了便於理解,通常咱們使用typedef關鍵字。
typedef float (*MyFuncPtrType)(int, char *);
若是你的函數指針指向一個型如float some_func(int, char *)的函數,這樣作就能夠了:
MyFuncPtrType my_func_ptr = some_func;
當你想調用它所指向的函數時,能夠這樣寫:
(*my_func_ptr)(7, "HelloWorld");
或者
my_func_ptr(7, "HelloWorld");
3.1.2 成員函數指針
在C++程序中,不少函數是成員函數,即這些函數是某個類中的一部分。你不能夠像一個普通的函數指針那樣指向一個成員函數,正確的作法應該是,你必須使用一個成員函數指針。一個成員函數的指針指向類中的一個成員函數,並有相同的參數,聲明以下:
float (SomeClass::*my_memfunc_ptr)(int, char *);
將函數指針指向型如float SomeClass::some_member_func(int, char *)的函數,能夠這樣寫:
    my_memfunc_ptr = &SomeClass::some_member_func;
當你想調用它所指向的成員函數時,能夠這樣寫:
SomeClass* x = new SomeClass;
(x->*my_memfunc_ptr)(6, "HelloWorld");
3.2 函數指針的大小
class A
{
public:
     int Afunc() { return 2; };
};
class B
{
public:
     int Bfunc() { return 3; };
};
class D: public A, public B
{
public:
     int Dfunc() { return 5; };
};
int main()
{
     printf("%d\n", sizeof(&main));
     printf("%d\n", sizeof(&A::Afunc));
     printf("%d\n", sizeof(&B::Bfunc));
     printf("%d\n", sizeof(&D::Dfunc));
     return 0;
}
輸出:
4
4
4
8
能夠看出,普通函數的指針大小是4,
普通類的成員函數的指針大小也是4,
對於多重繼承的類,成員函數的指針大小是8,
還有成員函數指針大小是12和16的狀況,在這裏就不展開了。
(須要特別注意的是,相同的代碼,在不一樣的編譯器下,函數指針的大小也不相同)。
對函數指針和成員函數指針的複習就到這裏。
3.3 C++中的事件委託
如下登場的是本文的主角:模板化實現的C++中的事件委託
3.3.1代碼
/////////////////////////////////////////////////////////////////////////////////
/// \class FuncCache
/// \brief 函數對象寄存器
/////////////////////////////////////////////////////////////////////////////////
template <typename ReturnType>
class FuncCache
{
     static const int SIZE = 48;
     typedef ReturnType (*func_caller)(FuncCache*);
     /// \class MemberFuncAssist
     /// \brief 對象成員函數寄存器的輔助器
     class FuncCacheAssist
     {
     public:
         /// \brief 構造函數,初始化。
         FuncCacheAssist(FuncCache* pFunc)
         {
              m_Size = 0;
              m_pFunc = pFunc;
              // 讀取用偏移必須歸位
              m_pFunc->m_Cur = 0;
         }
         /// \brief 析構函數。
         ~FuncCacheAssist(void)
         {
              // 彈出之前壓入的參數
              if (m_Size > 0)
                   m_pFunc->Pop(m_Size);
         }
         /// \brief 壓入指定大小的數據。
         uint Push(const void* pData, uint size)
         {
              m_Size += size;
              return m_pFunc->Push(pData, size);
         }
         /// 壓入參數的大小
         int                m_Size;
         /// 對象成員函數寄存器
         FuncCache*         m_pFunc;
     };
public:
     /// \brief 構造函數,初始化。
     FuncCache(func_caller func)
     {
         m_Size = 0;
         m_Cur = 0;
         m_Func = func;
     }
     /// \brief 壓入指定大小的數據。
     uint     Push(const void* pData, uint size)
     {
         size = (size <= SIZE - m_Size)? size : (SIZE - m_Size);
         memcpy(m_Buffer + m_Size, pData, size);
         m_Size += size;
         return size;
     }
     /// \brief 彈出指定大小的數據。
     uint      Pop(uint size)
     {
         size = (size < m_Size)? size : m_Size;
         m_Size -= size;
         return size;
     }
     /// \brief 讀取指定大小的數據,返回指針。
     void*         Read(uint size)
     {
         m_Cur += size;
         return (m_Buffer + m_Cur - size);
     }
     /// \brief 執行一個參數的函數。
     ReturnType    Execute(const void* pData)
     {
         // 用輔助結構控制
         FuncCacheAssist assist(this);
         // 壓入參數
         assist.Push(&pData, sizeof(void*));
         // 執行函數
         return m_Func(this);
     }
protected:
     /// 對象,函數,參數指針的緩衝區
     uchar         m_Buffer[SIZE];
     /// 緩衝區大小
     uint          m_Size;
     /// 緩衝區讀取用的偏移
     uint          m_Cur;
     /// 操做函數的指針
     func_caller   m_Func;
};
/////////////////////////////////////////////////////////////////////////////////
/// \class MFuncCall_1
/// \brief 一個參數的成員函數執行體
/////////////////////////////////////////////////////////////////////////////////
template <typename ReturnType, typename Caller, typename Func, typename ParamType>
class MFuncCall_1
{
public:
     /// \brief 執行一個參數的成員函數。
     static ReturnType MFuncCall(FuncCache<ReturnType>* pMFunc)
     {
         // 得到對象指針
         Caller* pCaller = *(Caller**)pMFunc->Read(sizeof(Caller*));
         // 得到成員函數指針
         Func func = *(Func*)pMFunc->Read(sizeof(Func));
         // 得到參數的指針
         ParamType* pData = *(ParamType**)pMFunc->Read(sizeof(ParamType*));
         // 執行成員函數
         return (pCaller->*func)(*pData);
     }
};
/////////////////////////////////////////////////////////////////////////////////
/// \class L_SignalRoot
/// \brief 類型檢查嚴格的事件委託器基類
/////////////////////////////////////////////////////////////////////////////////
template <typename ReturnType>
class L_SignalRoot
{
public:
     /// \brief 指定事件名,卸載指定對象的事件委託器。
     template <typename Caller>
     void     MFuncUnregister(Caller* pCaller)
     {
         func_map& func_list = m_MemberFuncMap;
         func_map::iterator it = func_list.find(pCaller);
         if (it != func_list.end())
              func_list.erase(it);
     }
     /// \brief 清空全部事件委託器。
     void     MFuncClear(void)
     {
         m_MemberFuncMap.clear();
     }
protected:
     typedef map< void*, FuncCache<ReturnType> > func_map;
     /// 事件名和綁定的事件委託器的列表
     func_map m_MemberFuncMap;
};
/////////////////////////////////////////////////////////////////////////////////
/// \class L_Signal_1
/// \brief 類型檢查嚴格,一個參數的事件委託器
/////////////////////////////////////////////////////////////////////////////////
template <typename ReturnType, typename ParamType>
class L_Signal_1 : public L_SignalRoot<ReturnType>
{
public:
     /// \brief 指定事件名,註冊對應的一個參數的事件委託器。
     template <typename Caller, typename Func>
     void     MFuncRegister(Caller* pCaller, Func func)
     {
         // 指定專門處理一個參數的函數執行體
         FuncCache<ReturnType> mfunc(MFuncCall_1<ReturnType, Caller, Func, ParamType>::MFuncCall);
         // 壓入對象和函數
         mfunc.Push(&pCaller, sizeof(Caller*));
         mfunc.Push(&func, sizeof(Func));
         // 添加到事件委託器列表
         m_MemberFuncMap.insert(make_pair(pCaller, mfunc));
     }
     /// \brief 指定事件名,調用其對應的一個參數的事件委託器。
     ReturnType    MFuncCall(const ParamType& data)
     { 
         // 清空返回值
         ReturnType result;
         memset(&result, 0, sizeof(result));
         // 對於全部委託器,調用註冊的函數
         func_map::iterator it = m_MemberFuncMap.begin();
         while (it != m_MemberFuncMap.end())
         {
              result = it->second.Execute(&data);
              ++it;
         }
         return result;
     }
};
class EventCallerA
{
public:
     bool Do(int event_id)
     {
         printf("EventCallerA do event %d.\r\n", event_id);
         return true;
     }
};
class EventCallerB
{
public:
     bool Run(int event_id)
     {
         printf("EventCallerB run event %d.\r\n", event_id);
         return true;
     }
};
void main()
{
     // 申明返回值是bool類型,參數是int類型,單參數的事件器
     L_Signal_1<bool, int> signal;
     EventCallerA callerA; 
     EventCallerB callerB;
// 註冊委託器並調用事件
     signal.MFuncRegister(&callerA, &EventCallerA::Do); 
     signal.MFuncRegister(&callerB, &EventCallerB::Run);
     signal.MFuncCall(1);
}
注意這裏EventCallerA和EventCallerB並無相同的基類。
3.3.2 名詞定義
先定義一些概念,便於咱們統一理解
事件器:指發生事件後,處理事件的響應,逐個通知事先註冊的對象。
委託器:指某事件發生後,須要被通知,並執行事先註冊的函數的對象。
3.3.3 需求
再談談咱們的需求:
1.     某個事件發生後,能通知到全部事先註冊過的委託器。
2.     委託器的類型可能千差萬別。
3.     加入參數使這個機制更靈活,應對每次不一樣的事件參數,支持1個,2個,甚至更多的參數。
4.     參數的類型也不但願有限制。
5.     委託器有執行結果,能夠被事件器獲取。
6.     委託器銷燬的時候,須要通知事件器,將其從委託器列表中排除。
3.3.4 限制
最後談談能夠有的限制
1.     針對同一個事件,委託器的函數參數類型應該是相同的,順序也相同,由於事件的參數類型是不變的,不然能夠分解爲兩個事件。由事件起始,通知委託器,若是參數類型各不相同,沒有意義。
2.     函數參數過多也沒有意義,由於咱們知道,多個參數的需求能夠用類或者結構體代替,以減小參數個數。
3.     大多數狀況下,咱們只須要知道最後一個委託器的執行結果。
3.3.5 解決方法
怎麼辦?抽象!
如何抽象??往二進制層面抽象!!
當咱們要統一處理一些需求的時候,咱們只有把需求當作相同的類型和格式。
說到底,對象只是內存中的一塊數據,而函數也是內存中的一段數據,咱們能夠用內存地址的方式來統一表示它們。
3.3.6 代碼解析
模板類FuncCache 就是爲了實現這一級的抽象而存在,如下對FuncCache作必要解析:
FuncCache::m_Buffer用來存儲對象的指針,成員函數的指針,以及函數參數的指針,暫定大小爲固定48字節,其中對象指針4字節,成員函數指針4到16字節不等,參數指針每一個爲4字節,能夠有多個。
FuncCache::m_Size表示目前用到了多少字節的數據。
FuncCache::m_Cur表示用來從頭依次讀取對象指針,成員函數指針和參數指針的數據偏移量。
FuncCache::m_Func很關鍵,由於以上都是數據,光有材料還要明確如何處理,其就承擔了這個重要的任務,類型是typedef ReturnType (*func_caller)(FuncCache*),對於同種ReturnType,其類型是固定的,這是很關鍵的一步,完成了從不一樣類型的對象,不一樣類型的函數以及參數(有條件的)到一致的對象之間的抽象。
FuncCache的各個函數很簡單,不作詳細說明了,值得一提的是其內部類FuncCacheAssist,這個內部類存在的主要價值是在Execute退出的時候,將壓入的參數排除。
接下來解析模板類MFuncCall_1
這裏只是列了對於一個參數的函數委託器的實現,無參數、多參數的實現相似,請讀者自行發揮。
該模板類很簡單,只有一個函數,可是提供了很靈活的功能,返回類型,對象類型,成員函數類型,參數類型,所有能夠自定義的,也只有經過模板類才能實現所須要的功能,具體函數算法很簡單,就不展開了。
最後是模板類L_SignalRoot和L_Signal_1,也就是事件器。
ReturnType是模板化的返回類型,ParamType是模板化的單參數類型。
該類只有一個變量m_MemberFuncMap,用來保存全部的委託器的抽象,也就是FuncCache。
該類提供了四個接口,也是對於使用者最常使用的:
1.       MFuncRegister  註冊委託器對象和函數
2.       MFuncCall       調用全部註冊的委託器,並返回最後調用的結果。
3.       MFuncUnregister 根據對象指針,刪除註冊過的委託器,委託器在事件器以前銷燬的話,             必須調用這個接口,這點沒有作成自動的,主要爲了減小類的複雜度。
4.       MFuncClear      清空全部委託器。
給用戶的接口很簡單明瞭易於使用。
相關文章
相關標籤/搜索