1. 程式人生 > >C++事件(Event)機制的實現一例(轉載)

C++事件(Event)機制的實現一例(轉載)

C++事件(Event)機制的實現一例[原始碼下載連結已修復] 作者:袁曉輝([email protected]) 宣告: 1、 本文為作者原創,如需轉載請保持本文的完整性並註明出自 www.farproc.com 和 http://blog.csdn.net/uoyevoli. 2、 本文附件中的原始碼你可以免費使用並無需註明出處。 用C++實現事件機制我以前寫過一個小例子,但不是很完善,比如Event只能接受全域性函式作為handler,類成員方法不可以,還有一個Event只能新增一個handler等……最近我的一個程式剛好要用到Event機制,所以我就抽了些時間,重新實現了一下。這個版本應該說是比較完善的,基本上和C#中的Event一樣了。點選這裡下載原始碼。 要使用Event機制主要用到兩個模板類:一個是Delegate,它實現了對C++函式指標的封裝,當然,如上所述包括類成員函式的指標;另一個是Event,顧名思義它就是主角“事件”了。用以實現Event機制的所有類都被我“圈”在CppEvent名稱空間裡了,以免“汙染”我們的global namespace。需要提醒的是,這個版本的實現中用到的C++的RTTI,所以用VC編譯是別忘了加上/GR編譯引數。 一 Delegate 先看一下Delegate類的原型: namespace CppEvent { … … /* *公開給程式使用的Delegate模板類 *ReturnType 函式返回值型別 *ArgsType 函式引數型別 */ template class Delegate { public: /* *建構函式 *用於包裝一個物件的成員函式 * object 成員函式所屬的物件指標 * function 成員函式的指標 */ template Delegate(ObjectType* object, ReturnType(ObjectType::*function)(ArgsType)) { … … } /* *建構函式 *用於包裝一個全域性函式/類static函式 * function 函式指標 */ Delegate(ReturnType(*function)(ArgsType)) { … … } }; } Delegate有兩個模板引數ReturnType和ArgsType,第一個指定被代理封裝的函式(全域性函式/類靜態方法或類非成員方法)的返回值型別,第二個指定這個函式的引數型別。注意,由於C++模板只能有固定個數的引數,所以Delegate必須對所封裝的函式的引數個數加以限制,也就是說Delegate引數的個數必須固定。既然個數必須固定,那就一個好了,反正我們可以傳遞結構作引數:)。 Delegate有兩個過載的建構函式。第二個是針對全域性函式或類靜態(static)函式的,只有一個引數,就是把ReturnType作為返回值且帶一個ArgsType型別引數的函式的指標(ReturnType(*function)(ArgsType) 注意C風格的函式指標的定義方法)。第一個建構函式是針對類的非靜態方法(成員方法)的,它本身也是“模板函式”,模板引數指定被它封裝的成員方法所屬的類。兩個引數,第一個為物件指標,第二個為成員方法的指標。比如有如下類: class C { public: int M (double param) { ...... return ......; } … … }; 且有一個類C的例項物件objc: C objc; 那麼,對於ojbc的M方法可以使用下面的DelegateM進行封裝: Delegate DelegateM (&objc, C::M); 有了delegate,就可以呼叫它的Invoke方法來呼叫它所封裝的函式,另外,Delegate過載了()操作符(operator)所以,你可以直接在Delegate物件後面加括號和引數進行呼叫了。比如int n =DelegateM.Invoke(0.2);或直接使用更簡便的形式int n= DelegateM(0.2); 二 Event 看一下Event的原型: namespace CppEvent { /* *事件 模板類 */ template class Event { public: /* *該事件處理函式所對應的代理型別 */ typedef Delegate EventHandler; public: /* *建構函式 */ Event() { } … … }; … … } 它有三個模板引數,分別為該事件代理(handler)的返回值型別、引數型別和一個標誌該事件是否為多播(multicast)的bool值。前兩個引數是說明可以用來“訂閱”(下面會說明)該事件的Delegate的函式(全域性/static或類成員)原型。所謂的“多播”指的是一個事件可以有多個訂閱的代理,也就是說當這個事件被激發時,可能會有多個函式被呼叫,MultiCast的預設值為true,即允許多播。 一旦定義了一個事件,就可以呼叫它的+=操作符來把一個合適型別的Delegate物件訂閱到改事件。-=運算子可以用來取消一個已經訂閱的Delegate物件。訂閱到某個事件的代理會在該事件被激發是被呼叫,該代理被從這個事件取消訂閱後就不會再被該事件呼叫了。 比如有如下Event型別: typedef CppEvent::Event BalanceChanged; 和一個該型別的Event: BalanceChanged OnBalanceChanged; 可以用如下的方法把一個全域性函式代理訂閱到該事件: OnBalanceChanged += Delegate (OnTomsAccountBalanceChanged); 其中OnTomsAccountBalanceChanged的原型如下: void OnTomsAccountBalanceChanged(size_t balance) { … … } 這樣,在OnBalanceChanged被激發時OnTomsAccountBalanceChanged函式就會被呼叫。激發一個事件可以有下面兩種方法: 呼叫Event的Invoke()方法 OnBalanceChanged.Invoke(100); 或直接利用Event的()操作符 OnBalanceChanged(100); 三 一個例子 下面我們以“銀行帳戶操作”為例子,來說明Event機制的使用。 首先是Account類: //Account.h #ifndef _ACCOUNT_H_ #define _ACCOUNT_H_ #include "../CppEvent/Event.h" namespace CppEventExampleAccount { class Account { public: //帳戶操作 enum OperationType{DepositOp/*存款*/, WithdrawOp/*取款*/}; class BalanceEventArgs { public: BalanceEventArgs(OperationType operation, size_t ammount) :theOperation(operation) ,theAmmount(ammount) { }; virtual ~BalanceEventArgs() { }; OperationType Operation(void) const { return theOperation; } size_t Amount(void) const { return theAmmount; } private: OperationType theOperation; size_t theAmmount; }; public: //定義"帳戶即將被改變"事件 //該事件接收一個BalanceEventArgs&引數,指定本次操作的具體資訊 //第三個模板引數false指定該事件為"單播(Singlecast)",即只能有一個handler typedef CppEvent::Event BalanceChanging; //定義"帳戶已改變"事件 typedef CppEvent::Event BalanceChanged; public: //定義兩個事件物件 BalanceChanging OnBalanceChanging; BalanceChanged OnBalanceChanged; public: //建構函式 Account(size_t balance = 0) :Balance(balance) { } virtual ~Account() { } public: //查詢餘額 size_t GetBalance(void) const { return Balance; } //存款方法 //引數指定所存金額 bool Deposit(size_t amount) { bool ReturnValue = false; //激發"帳戶即將被改變"事件,並接收其返回值 bool AllowOp = FireChangingEvent(DepositOp, amount); if(AllowOp)//如果允許改變 { //增加餘額 Balance += amount; //激發"帳戶已改變"事件 FireChangedEvent(); ReturnValue = true; } else { ReturnValue = false; } return ReturnValue; } //取款方法 //引數指定取款金額 bool Withdraw(size_t amount) { bool ReturnValue = false; if(Balance >= amount)//如果餘額足夠本次取款 { //激發"帳戶即將被改變"事件,並接收其返回值 bool AllowOp = FireChangingEvent(WithdrawOp, amount); if(AllowOp) { //減少餘額 Balance -= amount; //激發"帳戶已改變"事件 FireChangedEvent(); ReturnValue = true; } } else//餘額不足 { ReturnValue = false; } return ReturnValue; } protected: //激發"帳戶即將被改變"事件 bool FireChangingEvent(OperationType operation, size_t amount) { bool ReturnValue = false; if(OnBalanceChanging != NULL) { BalanceEventArgs args(operation, amount); ReturnValue = OnBalanceChanging(args); } else //如果該事件沒有handler則預設允許操作 { ReturnValue = true; } return ReturnValue; } //激發"帳戶已改變"事件 void FireChangedEvent(void) { OnBalanceChanged(Balance); } private: //帳戶餘額 size_t Balance; }; } #endif 然後定義要訂閱到Account兩個事件的全域性函式和MobilePhone類的成員函式: //Changing事件的handler bool OnTomsAccountBalanceChanging(Account::BalanceEventArgs& args) { TCHAR* OpName = args.Operation() == Account::DepositOp ? "存款" : "取款"; cout << "---系統日誌 : Tom的帳戶餘額即將被改動, " << OpName << args.Amount() << "元"; cout << " 此操作被允許---" << endl; return true; } //Changed事件的handler void OnTomsAccountBalanceChanged(size_t balance) { cout << "---系統日誌 : Tom的帳戶餘額被改動了,當前餘額為: " << balance << "元*---" << endl; } //手機 class MobilePhone { public: void OnAccountBalanceChanged(size_t amount) { cout << ">>>> 手機簡訊 : 尊敬的使用者,您的帳戶餘額發生了變化,當前餘額為" << amount << "元,請注意!" << endl; } bool OnAccountBalanceChanging(Account::BalanceEventArgs&) { cout << "該方法不會執行" << endl; return false; } //...... //更多的成員和方法 //...... }; 然後定義類的例項物件,並編寫main函式: //建立Tom的帳號 Account TomsAccount(99999); //Tom的手機 MobilePhone TomsPhone; int _tmain(int argc, _TCHAR* argv[]) { //為系統訂閱Tom的帳號的"帳戶即將被改動"事件 TomsAccount.OnBalanceChanging += Account::BalanceChanging::EventHandler(OnTomsAccountBalanceChanging); //上一行程式碼也可寫為: ........+= Delegate(OnTomsAccountBalanceChanging); //為系統訂閱Tom的帳號的"帳號已被改動"事件 TomsAccount.OnBalanceChanged += Account::BalanceChanged::EventHandler(OnTomsAccountBalanceChanged); //為Tom的手機訂閱他的帳號的"帳戶已被改動"事件 TomsAccount.OnBalanceChanged += Account::BalanceChanged::EventHandler(&TomsPhone, MobilePhone::OnAccountBalanceChanged); //為Tom的手機訂閱Tom的帳號的"帳號已被改動"事件 //由於該事件為Singlecast,所以此訂閱會失敗 TomsAccount.OnBalanceChanging += Account::BalanceChanging::EventHandler(&TomsPhone, MobilePhone::OnAccountBalanceChanging); bool Success = false; //執行一些操作,激發一些事件.相應的事件handler函式會呼叫 cout << "即將執行 存款100元 的操作" << endl; Success = TomsAccount.Deposit(100); cout << (Success ? "操作成功!" : "操作失敗!") << endl << endl; cout << "即將執行 存款5000元 的操作" << endl; Success = TomsAccount.Deposit(5000); cout << (Success ? "操作成功!" : "操作失敗!") <>>> 手機簡訊 : 尊敬的使用者,您的帳戶餘額發生了變化,當前餘額為100099元,請注意! 操作成功! 即將執行 存款5000元 的操作 ---系統日誌 : Tom的帳戶餘額即將被改動, 存款5000元 此操作被允許--- ---系統日誌 : Tom的帳戶餘額被改動了,當前餘額為: 105099元*--- >>>> 手機簡訊 : 尊敬的使用者,您的帳戶餘額發生了變化,當前餘額為105099元,請注意! 操作成功! 即將執行 取款10000元 的操作 ---系統日誌 : Tom的帳戶餘額即將被改動, 取款10000元 此操作被允許--- ---系統日誌 : Tom的帳戶餘額被改動了,當前餘額為: 95099元*--- >>>> 手機簡訊 : 尊敬的使用者,您的帳戶餘額發生了變化,當前餘額為95099元,請注意! 操作成功! -應Tom的要求,退訂了他的手機簡訊通知- 即將執行 取款50元 的操作 ---系統日誌 : Tom的帳戶餘額即將被改動, 取款50元 此操作被允許--- ---系統日誌 : Tom的帳戶餘額被改動了,當前餘額為: 95049元*--- 操作成功! Press any key to continue 在main函式裡對Account的例項TomsAccount進行操作時,訂閱了指定事件的函式和方法會被呼叫,而當退訂了某個函式或方法後,該函式或方法就不再在事件激發時被呼叫了。 以上程式碼在vc7.1中除錯通過。最後再提醒一遍,別忘了VC的/GR引數哦。