C++事件(Event)機制的實現
用C++實現事件機制我以前寫過一個小例子,但不是很完善,比如Event只能接受全域性函式作為handler,類成員方法不可以,還有一個Event只能新增一個handler等……最近我的一個程式剛好要用到Event機制,所以我就抽了些時間,重新實現了一下。這個版本應該說是比較完善的,基本上和C#中的Event
要使用Event機制主要用到兩個模板類:一個是Delegate,它實現了對C++函式指標的封裝,當然,如上所述包括類成員函式的指標;另一個是Event,顧名思義它就是主角“事件”了。用以實現Event機制的所有類都被我“圈”在CppEvent名稱空間裡了,以免“汙染”我們的global namespace
一 Delegate
先看一下Delegate類的原型:
namespace CppEvent
{
……
/*
*公開給程式使用的Delegate模板類
*ReturnType 函式返回值型別
*ArgsType 函式引數型別
*/
template <typename ReturnType = void , typename ArgsType = void*>
class Delegate
{
public:
/*
*建構函式
*用於包裝一個物件的成員函式
* object 成員函式所屬的物件指標
* function 成員函式的指標
*/
template<class ObjectType>
Delegate(ObjectType* object,ReturnType(ObjectType::*function)(ArgsType))
{
……
}
/*
*建構函式
*用於包裝一個全域性函式/類static函式
* function 函式指標
*/
Delegate(ReturnType(*function)(ArgsType))
{
……
}
};
}
Delegate有兩個模板引數ReturnType和ArgsType,第一個指定被代理封裝的函式(全域性函式/類靜態方法或類非成員方法)的返回值型別,第二個指定這個函式的引數型別。注意,由於
Delegate有兩個過載的建構函式。第二個是針對全域性函式或類靜態(static)函式的,只有一個引數,就是把ReturnType作為返回值且帶一個ArgsType型別引數的函式的指標(ReturnType(*function)(ArgsType) 注意C風格的函式指標的定義方法)。第一個建構函式是針對類的非靜態方法(成員方法)的,它本身也是“模板函式”,模板引數指定被它封裝的成員方法所屬的類。兩個引數,第一個為物件指標,第二個為成員方法的指標。比如有如下類:
class C
{
public:
int M (double param)
{
......
return ......;
}
……
};
且有一個類C的例項物件objc:
C objc;
那麼,對於ojbc的M方法可以使用下面的DelegateM進行封裝:
Delegate<int, double> 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 <typename ReturnType = void, typename ArgsType = void*, bool MultiCast = true>
class Event
{
public:
/*
*該事件處理函式所對應的代理型別
*/
typedef Delegate<ReturnType, ArgsType> EventHandler;
public:
/*
*建構函式
*/
Event()
{
}
……
};
……
}
它有三個模板引數,分別為該事件代理(handler)的返回值型別、引數型別和一個標誌該事件是否為多播(multicast)的bool值。前兩個引數是說明可以用來“訂閱”(下面會說明)該事件的Delegate的函式(全域性/static或類成員)原型。所謂的“多播”指的是一個事件可以有多個訂閱的代理,也就是說當這個事件被激發時,可能會有多個函式被呼叫,MultiCast的預設值為true,即允許多播。
一旦定義了一個事件,就可以呼叫它的+=操作符來把一個合適型別的Delegate物件訂閱到改事件。-=運算子可以用來取消一個已經訂閱的Delegate物件。訂閱到某個事件的代理會在該事件被激發是被呼叫,該代理被從這個事件取消訂閱後就不會再被該事件呼叫了。
比如有如下Event型別:
typedef CppEvent::Event<void, size_t> BalanceChanged;
和一個該型別的Event:
BalanceChanged OnBalanceChanged;
可以用如下的方法把一個全域性函式代理訂閱到該事件:
OnBalanceChanged += Delegate<void, size_t> (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<bool, BalanceEventArgs&, false> BalanceChanging;
//定義"帳戶已改變"事件
typedef CppEvent::Event<void, size_t> 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: