您的位置:首页 > 编程语言 > C语言/C++

C++事件(Event)机制的实现

2011-10-19 23:26 489 查看

C++事件(Event)机制的实现

2007-10-01 14:13 752人阅读 评论(0)
收藏 举报

用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有两个模板参数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<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:
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<bool ,Account::BalanceEventArgs&>(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 ? "操作成功!" : "操作失败!") <<endl << endl;

cout << "即将执行 取款10000元 的操作" << endl;
Success = TomsAccount.Withdraw(10000);
cout << (Success ? "操作成功!" : "操作失败!") <<endl << endl;

cout << "-应Tom的要求,退订了他的手机短信通知-"<< endl;
//退订了Tom的手机短信通知
TomsAccount.OnBalanceChanged -= Account::BalanceChanged::EventHandler(&TomsPhone, MobilePhone::OnAccountBalanceChanged);
//退订后TomsPhone将不再接收本事件
cout << "即将执行 取款50元 的操作" << endl;
Success = TomsAccount.Withdraw(50);
cout << (Success ? "操作成功!" : "操作失败!") <<endl << endl;

}

运行该程序,可以看到输出如下:

即将执行 存款100元 的操作
---系统日志 : Tom的帐户余额即将被改动, 存款100元 此操作被允许---
---系统日志 : Tom的帐户余额被改动了,当前余额为: 100099元*---
>>>> 手机短信 : 尊敬的用户,您的帐户余额发生了变化,当前余额为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参数哦。

<完>

分享到:

上一篇: C++ 快速内存分配
下一篇:用C++封装Socket库
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: