事件支援已經是Delphi,Java,C#這樣的後起語言的文法之一,但是在C++中並沒有顯示的支援。不同的編譯器採用各自的方法來提供對事件的支援,例如:Borland C++ Builder通過擴充文法來提供事件支援,以實現VCL;MFC的事件是在設計時由嚮導產生的基於表格驅動的靜態事件,不提供運行時支援。這兩種實現都只能在特定情境下工作,而且難於移植。因此,我們需要一個完全基於C++文法的事件機制,不能使用編譯器對C++文法的擴充關鍵字,並且,該機制應該是動態,即能夠在運行時改變事件屬性,這個屬性通常是指與事件相關聯的事件處理過程的方法或函數的地址,能夠動態個性屬性的事件具有更大的靈活性和便於使用。 要實現這一目的,可以參考一下Delphi,Java,C#,包括BCB的VCL,在這些語言中(或開發工具)中事件的實現或表示機制,還可以參考一下COM的連接點技術。很容易就可以發現,它們有一個共同點:所有對象都源自一個公用基類,尤其是BCB,如果對象不是繼承自TObject的話,就不能使用事件。參考COM的連接點實現,也可以發現一些有意思的東西,特別是它調用連接點事件的手段。同時,MFC建立事件表格的方法,也值得借鑒。 使用公用基類,派生對象可以向上映射,這樣就可以對事件的表現形式進行統一定義。使用公用基類還有另外一個重要意義:當我們在C++中擷取對象的方法地址時,實際上取得是對象方法在虛表中位移,而源自相同基類的對象的方法的位移是各不相同的,即它們的虛表具有統一的布局,而源自不同基類的對象的方法則可能相同。 在進行下一步討論之前,我們先看看在C++中如何通過指標來調用對象的方法。 在C++中,可以通過以下形式來獲得一個對象的方法地址:
&對象::方法 這樣得到的地址其實是方法在對象的虛表內的16位位移,因此,還需要一個對象的執行個體指標才能正確的調用方法。可以使用以下語句來調用執行個體中的方法:
(對象的執行個體指標->*方法地址)(參數) 通常,我們需要將方法地址映射為一個通用的表現形式:
static_cast<通用的方法表現形式>(&對象::方法) 舉例來說,對於兩個類A,B的方法A::func1,B::func2,當它們源自相同基類時,則在虛表中具有不同位移,一個可能為1,另一個可能為5,但絕不會相同,假設取得的地址為:addr1,addr2。那麼就有: addr1 = static_cast<表現定義>(&A::func1); addr2 = static_cast<表現定義>(&B::func2); 無論何時,如果按以下形式調用兩個方法,則是正確的: (對象X指標->*addr1)();// 這會調用位移1處的方法 (對象Y指標->*addr2)();// 這會調用位移5處的方法 但是,當它們源自不同基類時,則無法進行統一形式的static_cast映射,並且,方法有可能具有相同的位移,這樣兩個調用都會嘗試同一個方法,這會導致錯誤。 通過公用基類,可以將事件的處理方法屬性關聯到任意符合定義的方法上,這樣就可以在運行時修改事件屬性,實現動態事件機制。當然,在設定事件的關聯處理方法-通常叫事件控制代碼(handler)-的同時,我們還需要儲存一個具有該方法的對象的執行個體指標。這樣當事件發生時,我們就能夠在正確的對象執行個體上調用正確的方法,完成正確的事件處理。 下面是基於上面方法的的實現代碼,使用了BCB的命名習慣和doxgen的注釋風格: /*** 基類*/class TObject {}; /*** 事件參數類*/template < class T >class TEventArg : public TObject{ T value;public: TEventArg():value() { } TEventArg(T x) { value = x; } TEventArg(const TEventArg& ref) { value = ref; } TEventArg<T>& operator = (T x) { value = x; return *this; } TEventArg<T>& operator = (TEventArg<T> x) { value = x; return *this; } operator T() { return value; }}; /*** 事件類別*/template < class T>class TEvent : public TObject{public: /** * 事件控制代碼類型定義 */ typedef void (TObject::*TEventHandler)(TEventArg<T>[]); private: /** * 儲存事件處理方法的控制代碼數組 * 通常只要儲存一個事件處理方法就可以* 這裡是為了示範一個事件上激發多個處理控制代碼的情況,使用了數組來* 儲存所有控制代碼*/ vector<pair<TObject*, TEventHandler> > _HandlerArray; public: /** * 清除所有事件處理方法的控制代碼 */ void clear() { _HandlerArray.clear(); } /** * 移除事件處理方法控制代碼 */ void remove(pair<TObject*, TEventHandler> handler) { // 通常事件只有一個或數個,所以為了簡單起見和說明用途,使用了for迴圈 for ( vector<pair<TObject*, TEventHandler> >::iterator it = _HandlerArray.begin(); it != _handlerArray.end(); it++ ) { if ( (*it).first == handler.first && *(int *).second == *(int *)handler.second ) { it = _HandlerArray.erase(it); } } } /** * 控制代碼的賦值操作,清空已有的控制代碼,並用新控制代碼代替 * @see add */ TEvent<T>& operator = (pair<TObject*, TEventHandler> handler) { clear(); _HandlerArray.push_back(handler); return *this; } /** * 添加事件處理方法控制代碼,在實際情況中,比operator +=操作簡單 */ void add(TObject* object, TEventHandler handler) { _HandlerArray.push_back(make_pair<TObject*, TEventHandler>(object, handler)); } /** * 添加事件處理方法控制代碼 */ TEvent<T>& operator += (pair<TObject*, TEventHandler> handler) { _HandlerArray.push_back(handler); return *this; } /** * 刪除控制代碼 */ TEvent<T>& operator += (pair<TObject*, TEventHandler> handler) { remove(handler); return *this; } /** * 激發事件 */ void fire(TEventArg<T> args[]) { /** * 迴圈激發所有事件 */ for(vector<pair<TObject*, TEventHandler> >::iterator it = _HandlerArray.begin(); it != _HandlerArray.end(); it++) { if ( (*it).first != NULL && (*it).second != NULL ) { // 這裡是示意如何呼叫事件控制代碼,形成激發事件的 // 為了示意,寫成三步 TObject* object = (*it).first; TEventHandler handler = (*it).second; (object->*handler)(args); //((*it).first->*((*it).second))(args); // 簡短的形式一步就可以了 } // if }// for }// method fire}; /*** 事件的激發源*/template < class T >class TMyEventFirer : public TObject{public: /// 事件 TEvent<T> MyEvent; /// 某個將會激發事件的方法 void fireEvent(TEventArg<T> args[]) { MyEvent.fire(args); }}; /*** 處理事件的對象之一*/class A : public TObject{public: A() { } public: /** * 事件處理方法 */ void func(TEventArg<int> args[]) { cout<<"A:func:"<<(int)args[0]<<endl; }}; /*** 處理事件的對象之二*/class B : public TObject{public: B() { } public: /** * 事件處理方法 */ void func(TEventArg<int> args[]) { cout<<"B:func:"<<(int)args[0]<<endl; }}; int _tmain(int argc, _TCHAR* argv[]){ TMyEventFirer<int> firer; // 事件來源 A a; // 事件處理對象 B b; // 事件處理對象 //添加A對象的事件處理 firer.MyEvent += make_pair<TObject*, TEvent<int>::TEventHandler>(&a, static_cast<TEvent<int>::TEventHandler>(&A::func)); //添加B對象的事件處理 firer.MyEvent += make_pair<TObject*, TEvent<int>::TEventHandler>(&b, static_cast<TEvent<int>::TEventHandler>(&B::func)); // 再次添加A對象的事件處理 // 由於沒有判別唯一性,所以A對象的事件處理會被激發次 firer.MyEvent.add(&a, static_cast<TEvent<int>::TEventHandler>(&A::func)); //準備參數 TEventArg<int> args[1]; // 初始化 args[0] = 999; // 激發事件 firer.fireEvent(args); return 0;} 這個粗陋的例子應該足夠示範如何?動態事件機制了,剩下的就請自行充實完成。有空請訪問:俏皮網 http://www.qpix.cn