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

C++事件(Event)機制的實現

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 <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有兩個模板引數ReturnTypeArgsType,第一個指定被代理封裝的函式(全域性函式/類靜態方法或類非成員方法)的返回值型別,第二個指定這個函式的引數型別。注意,由於

C++模板只能有固定個數的引數,所以Delegate必須對所封裝的函式的引數個數加以限制,也就是說Delegate引數的個數必須固定。既然個數必須固定,那就一個好了,反正我們可以傳遞結構作引數:)

Delegate有兩個過載的建構函式。第二個是針對全域性函式或類靜態(static)函式的,只有一個引數,就是把ReturnType作為返回值且帶一個ArgsType型別引數的函式的指標(ReturnType(*function)(ArgsType) 注意C風格的函式指標的定義方法)。第一個建構函式是針對類的非靜態方法(成員方法)的,它本身也是“模板函式”,模板引數指定被它封裝的成員方法所屬的類。兩個引數,第一個為物件指標,第二個為成員方法的指標。比如有如下類:

class C

{

public:

int M (double param)

{

......

return ......;

}

};

且有一個類C的例項物件objc

C objc;

那麼,對於ojbcM方法可以使用下面的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函式就會被呼叫。激發一個事件可以有下面兩種方法:

呼叫EventInvoke()方法

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: