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

在C++中模拟委托事件的方法(四)

2011-11-22 12:40 357 查看
三、静态函数方式实现多对象事件接收

对应的例子工程名MultiObjectEvent

在静态函数模拟事件方法中,如果读者细心的话,会发现类CNotifyClass只包含一个指定对象的成员变量m_pEventParameter,在使用RegisterEvent方法注册接收事件的函数时如果不接收某些事件,就把相应的函数参数设为NULL,但是接收事件的对象参数只能是一个,这样就不能有多个类CRecvEventClass的实例接收事件,但是有的时候因为类CNotifyClass是一个服务类,就可能存在多个接收类的实例同时接收不同的事件,这个时候在静态函数模拟事件方法中将无能为力,所以要实现多个类对象能接收事件,就必须分开注册方法RegisterEvent,同时允许注册不同的pParameter,这个的具体实现代码在MultiObjectEvent例子中,我们看看类CNotifyClass的声明

typedef void (*PEVENT_NOPARAM_NORETURN)(void *);
typedef int (*PEVENT_NOPARAM_RETURN)(void *);
typedef void (*PEVENT_PARAM_NORETURN)(void *, int);
typedef int (*PEVENT_PARAM_RETURN)(void *, int);

class CNotifyClass
{
public:
CNotifyClass(void);
~CNotifyClass(void);

public:
bool RegisterEvent(PEVENT_NOPARAM_NORETURN pFunc, void *pParameter);
void UnRegisterNoParamNoRetEvent();
bool RegisterEvent(PEVENT_NOPARAM_RETURN pFunc, void *pParameter);
void UnRegisterNoParamRetEvent();
bool RegisterEvent(PEVENT_PARAM_NORETURN pFunc, void *pParameter);
void UnRegisterParamNoRetEvent();
bool RegisterEvent(PEVENT_PARAM_RETURN pFunc, void *pParameter);
void UnRegisterParamRetEvent();

void UnRegisterAllEvent();

int DoNotifyEventWork();

protected:
PEVENT_NOPARAM_NORETURN m_pNoParam_NoReturn_EventHandler;
PEVENT_NOPARAM_RETURN m_pNoParam_Return_EventHandler;
PEVENT_PARAM_NORETURN m_pParam_NoReturn_EventHandler;
PEVENT_PARAM_RETURN m_pParam_Return_EventHandler;

protected:
void *m_pNoParamNoRetEventParameter;
void *m_pNoParamRetEventParameter;
void *m_pParamNoRetEventParameter;
void *m_pParamRetEventParameter;
};

分开事件的注册,同时允许不同的参数传递进入,保存在多个参数成员变量中,来实现多个不同类对象接收事件的方法,可以看到存在大量的RegisterEvent和UnRegisterParamRetEvent方法,正是由于这种方法代码量比较大,书写非常麻烦,容易出错,而且后面介绍的方法更好,所以不推荐这种方法,这里就不详述了,有兴趣的读者自己去看例子程序。

四、静态函数与类模板结合模拟事件

对应的例子工程名DelegateEvent

为了解决多个对象接收不同的事件的问题,同时规范化程序的编写,我们这里使用C++模板类的方法来定义一个委托类管理事件

1、 具体的实现方法

(1)、委托类模板的定义与实现

namespace dpex
{
template <class F>
class CDelegate
{
public:
CDelegate(void)
: m_pParameter(NULL), m_Func(NULL)
{
}

~CDelegate(void)
{
UnRegisterEvent();
}

public:
bool RegisterEvent(F func, void *pParameter)
{
if (NULL != m_Func)
return false;
m_Func = func;
m_pParameter = pParameter;
return true;
}

void UnRegisterEvent()
{
m_pParameter = NULL;
m_Func = NULL;
}

void GetEventAndParam(F *pFunc, void **ppParameter)
{
*pFunc = m_Func;
*ppParameter = m_pParameter;
}

private:
void *m_pParameter;
F m_Func;
};
}

委托模板类CDelegate使用指向函数的指针作为模板参数,在类中保存一个接收事件的模板函数指针作为成员m_Func,同时保存接收这个事件的参数(通常是事件接收类对象的指针)m_pParameter,同时提供注册与反注册事件的方法RegisterEvent 与UnRegisterEvent,注意方法RegisterEvent的参数正是F func和void *pParameter,同时为了事件触发类访问到具体的接收事件的函数指针以及要传递的参数,提供了GetEventAndParam方法来获取这2个数据。

看到这里可能还不容易看出这个模板类具体的使用方法,现在看看事件触发类的定义。

(2)、事件触发对象类CNotifyClass的类定义如下:

#include "../Delegate/Delegate.h"

using dpex::CDelegate;

typedef void (*PEVENT_NOPARAM_NORETURN)(void *);
typedef int (*PEVENT_NOPARAM_RETURN)(void *);
typedef void (*PEVENT_PARAM_NORETURN)(void *, int);
typedef int (*PEVENT_PARAM_RETURN)(void *, int);

class CNotifyClass
{
public:
CNotifyClass(void);
~CNotifyClass(void);

public:
int DoNotifyEventWork();

public:
CDelegate<PEVENT_NOPARAM_NORETURN> m_pNoParam_NoReturn_EventHandler;
CDelegate<PEVENT_NOPARAM_RETURN> m_pNoParam_Return_EventHandler;
CDelegate<PEVENT_PARAM_NORETURN> m_pParam_NoReturn_EventHandler;
CDelegate<PEVENT_PARAM_RETURN> m_pParam_Return_EventHandler;
};

类实现如下:

#include "NotifyClass.h"

CNotifyClass::CNotifyClass(void)
{
}

CNotifyClass::~CNotifyClass(void)
{
m_pNoParam_NoReturn_EventHandler.UnRegisterEvent();
m_pNoParam_Return_EventHandler.UnRegisterEvent();
m_pParam_NoReturn_EventHandler.UnRegisterEvent();
m_pParam_Return_EventHandler.UnRegisterEvent();
}

int
CNotifyClass::DoNotifyEventWork()
{
int iResult = 0;
PEVENT_NOPARAM_NORETURN func1;
PEVENT_NOPARAM_RETURN func2;
PEVENT_PARAM_NORETURN func3;
PEVENT_PARAM_RETURN func4;
void *pParameter;

m_pNoParam_NoReturn_EventHandler.GetEventAndParam(&func1, &pParameter);
if (func1 != NULL)
func1(pParameter);
m_pNoParam_Return_EventHandler.GetEventAndParam(&func2, &pParameter);
if (func2 != NULL)
iResult = func2(pParameter);

iResult = iResult + 10;
m_pParam_NoReturn_EventHandler.GetEventAndParam(&func3, &pParameter);
if (func3 != NULL)
func3(pParameter, iResult);
iResult = iResult + 10;
m_pParam_Return_EventHandler.GetEventAndParam(&func4, &pParameter);
if (func4 != NULL)
iResult = func4(pParameter, iResult);

return iResult;
}

从以上代码就可以看出来这种方法的基础仍然是静态成员函数模拟接收事件的方法,仍然需要声明事件处理函数的格式,不同的是不再定义注册与反注册的方法,也没有事件需要传递的参数m_pParameter那个成员变量了,代替成了定义公有的CDelegate类型的成员变量,具体代码类似如下

CDelegate<PEVENT_NOPARAM_NORETURN> m_pNoParam_NoReturn_EventHandler;

事件接收类的对象通过这个成员变量调用CDelegate模板类方法注册事件,事件触发类当要触发事件时使用类似如下的代码来调用

PEVENT_NOPARAM_NORETURN func1;
void *pParameter;
m_pNoParam_NoReturn_EventHandler.GetEventAndParam(&func1, &pParameter);
if (func1 != NULL)
func1(pParameter);

这样就可以触发事件,通知事件接收类来处理了。

(3)、事件接收对象类或事件处理对象类CRecvEventClassOne的类定义如下:

#include "NotifyClass.h"

class CRecvEventClassOne
{
public:
CRecvEventClassOne(CNotifyClass *pncNotify);
~CRecvEventClassOne(void);

public:
int DoWork(int iArg);

protected:
static void OnNoParamNoReturnEvent(void *pvParam);
static int OnNoParamReturnEvent(void *pvParam);

protected:
CNotifyClass *m_pncNotify;
int m_nNum;
};

类实现如下:

#include "RecvEventClassOne.h"

CRecvEventClassOne::CRecvEventClassOne(CNotifyClass *pncNotify)
{
if (NULL == pncNotify)
return;
m_pncNotify = pncNotify;
m_pncNotify->m_pNoParam_NoReturn_EventHandler.RegisterEvent(OnNoParamNoReturnEvent, this);
m_pncNotify->m_pNoParam_Return_EventHandler.RegisterEvent(OnNoParamReturnEvent, this);
}

CRecvEventClassOne::~CRecvEventClassOne(void)
{
if (NULL == m_pncNotify)
return;
m_pncNotify->m_pNoParam_NoReturn_EventHandler.UnRegisterEvent();
m_pncNotify->m_pNoParam_Return_EventHandler.UnRegisterEvent();
m_pncNotify = NULL;
}

int
CRecvEventClassOne::DoWork(int iArg)
{
int iRet;
m_nNum = iArg;
_tprintf(_T("CRecvEventClassOne m_num is %d\n"), m_nNum);
iRet = m_pncNotify->DoNotifyEventWork();
return iRet;
}

void
CRecvEventClassOne::OnNoParamNoReturnEvent(void *pvParam)
{
_tprintf(_T("Run CRecvEventClassOne::OnNoParamNoReturnEvent\n"));
if (pvParam != NULL)
{
CRecvEventClassOne *p = reinterpret_cast<CRecvEventClassOne *>(pvParam);
p->m_nNum = p->m_nNum + 10;
_tprintf(_T("CRecvEventClassOne m_num is %d\n"), p->m_nNum);
}
}

int
CRecvEventClassOne::OnNoParamReturnEvent(void *pvParam)
{
_tprintf(_T("Run CRecvEventClassOne::OnNoParamReturnEvent\n"));
if (pvParam != NULL)
{
CRecvEventClassOne *p = reinterpret_cast<CRecvEventClassOne *>(pvParam);
p->m_nNum = p->m_nNum + 10;
_tprintf(_T("CRecvEventClassOne m_num is %d\n"), p->m_nNum);
return p->m_nNum;
}
else
return 0;
}

事件接收类要定义满足事件接收函数声明格式的静态成员方法来接收事件,在注册事件时使用类似如下的方法来注册

m_pncNotify->m_pNoParam_NoReturn_EventHandler.RegisterEvent(OnNoParamNoReturnEvent, this);

m_pncNotify是事件触发类CNotifyClass类实例指针,通过它的成员变量m_pNoParam_NoReturn_EventHandler的方法来注册,同样使用类似如下代码来反注册事件

m_pncNotify->m_pNoParam_NoReturn_EventHandler.UnRegisterEvent();

这样就可以实现事件的挂接接收事件,然后进行一定的处理了。

(4)、使用的例子及输出

int _tmain(int argc, _TCHAR* argv[])
{
CNotifyClass ncNotify;
CRecvEventClassOne rec1(&ncNotify);
CRecvEventClassTwo rec2(&ncNotify);
int iIn, iOut;

iIn = 10;
iOut = rec1.DoWork(iIn);
_tprintf(_T("DelegateEvent test, Init:%d, Result:%d\n"), iIn, iOut);

TCHAR c;
_tscanf(_T("%c"), &c);
return 0;
}

输出结果为:

CRecvEventClassOne m_num is 10
Run CRecvEventClassOne::OnNoParamNoReturnEvent
CRecvEventClassOne m_num is 20
Run CRecvEventClassOne::OnNoParamReturnEvent
CRecvEventClassOne m_num is 30
Run CRecvEventClassTwo::OnParamNoReturnEvent
CRecvEventClassTwo m_num is 50
Run CRecvEventClassTwo::OnParamReturnEvent
CRecvEventClassTwo m_num is 110
DelegateEvent test, Init:10, Result:110

从输出结果上看2个不同对象接收了同一事件触发对象的不同事件,并分别进行了一定的工作,数据的数值被改变了,这些工作既有事件触发对象对于数值的修改也有事件接收对象对于数据的修改。

2、 实现的要点

(1)、委托类的实现要点

a、 使用模板类的方法定义类,同时把要定义成事件的函数声明作为模板参数

b、 成员变量要包含事件函数的成员,与要传递的参数成员

c、 定义注册与反注册事件的方法,支持事件的挂接与取消,方便事件接收类调用来注册事件

d、 如果b中的2个成员变量访问权限不是public,需要实现获取这两个成员的方法,方便事件触发类来通过这个方法取得这两个量进行触发事件的调用,这个方法主要是被事件触发类调用

值得说明的是,这个委托模板类一旦定义完成,基本不需要改变,具体应用中只需要在实现事件触发类和事件接收类时使用就可以了,即实现这个模板类的工作量是一次性的。

(2)、事件触发类的实现要点

a、 事件触发类必须定义要处理事件的函数声明

b、 事件触发类要使用事件函数声明作为模板参数定义委托类CDelegate的成员变量

CDelegate<PEVENT_NOPARAM_NORETURN> m_pNoParam_NoReturn_EventHandler;

c、 在工作时,需要触发事件的地方,通过使用CDelegate的方法获取事件函数指针和需要传递的参数,然后使用这个参数调用事件函数。

(3)、事件接收对象类或事件处理对象类的实现要点

a、 使用静态成员方法定义与要接收的事件函数声明相同的方法

b、 使用事件触发类的响应事件委托类成员变量的RegisterEvent和UnRegisterEvent方法注册与反注册是否接收事件,在注册事件时根据需要传递自身this来作为参数

c、 在事件处理的静态方法中,根据需要转换参数为当前对象,然后进行工作

3、 优缺点

(1)、优点

a、可以根据需要选择需要接收的事件

b、事件处理方法不需要必须是public的方法,任意访问类别都可以

c、可以让不同的对象接收同一个事件触发类(服务类)的不同事件

d、委托模板类一次性开发后,直接使用,在事件触发类和事件接收类中的代码简洁,代码量少,容易理解,不容易出错。

(2)、缺点

针对事件接收类对象的指针参数仍然被转化为void*,相应的其类型安全性相对差些,但是对于事件处理函数的指针因为是模板类参数,所以安全性没有问题,可以在编译期间被检查

这里多说一下关于类型安全的问题,委托类CDelegate可以不定义成模板类,让事件处理函数的指针也统一使用void *进行类型转换,但是这种转换的不安全非常高,这种不安全性还不同于事件接收类对象的指针参数定义成void *,原因就是事件接收类对象的指针参数m_pParameter是被事件接收类来传递进入的,同时使用的时候,也是事件接收类的事件处理的静态方法来使用转换的,所以只有事件接收类在对这个参数进行处理,事件触发类只是传递一下,并不处理与识别其类型,即只有一个类和这个变量m_pParameter有关联,所以其类型的不安全不会造成很大的问题;对于事件处理函数的指针如果也定义成void
*,虽然可行,但是传递接入是事件接收类,进行类型转换,并进行调用的是事件触发类,所以这里关系到两个类,定义与使用的类不同,这种情况下出问题的可能性大大增加,所以要使用模板类来处理这个问题,避免这种危险的类型转换。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: