转自:http://blog.csdn.net/gogogo/article/details/6999960
四、静态函数与类模板结合模拟事件
对应的例子工程名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 *,虽然可行,但是传递接入是事件接收类,进行类型转换,并进行调用的是事件触发类,所以这里关系到两个类,定义与使用的类不同,这种情况下出问题的可能性大大增加,所以要使用模板类来处理这个问题,避免这种危险的类型转换
五、事件链模拟事件
对应的例子工程名DelegateChainEvent
类模板的方法已经可以比较方便的让不同的对象接收同一个事件触发类的不同事件了,大多数的开发需要都能满足了,不过如果用过C#的人就会看到其中的一些问题,就是事件链的问题,因为C#允许对事件进行”+=”和”-=”的操作,即事件触发类的一个事件是一个事件链,可以挂接多个事件处理方法,当事件触发类这一个事件触发时,可以通知多个事件处理方法来处理事件,这种事件链的好处是,针对一个事件触发类(服务类)触发一个事件时,可以通知多个事件接收类都进行处理,比如界面上一个进度条图形显示进度,一个标签显示工作进度的具体文字信息,这样可以让进度条对象和标签对象同时接收一个工作类的progress事件,这个progress事件是一个事件链,当有了新的进度变化时,就调用progress事件链上的每个事件处理程序,进度条对象和标签对象都可以得到通知,各自更新自己的进度显示。
在以上3种比较可行的模拟事件方法中,事件处理函数的声明可以是任意的,可以有返回值,可以传递指针类型的形参或者引用类型的形参,即事件处理函数可以返回值,正如C#一样,如果采用事件链的方式,由于事件链中的函数被一个个调用时如果允许返回值(包括通过形参来返回),调用事件链中的下一个事件处理函数时不能确定怎么处理返回值,所以如果模拟事件链,就要求所有的事件处理函数的声明不能返回任何值(包括不能通过形参返回值)。
1、 具体的实现方法
(1)、委托模板类的定义与实现
- #include <vector>
- #include <utility>
- #include <algorithm>
- using std::vector;
- using std::pair;
- namespace dpex
- {
- template <class F>
- class CDelegateChain
- {
- public:
- CDelegateChain(void)
- {
- }
- ~CDelegateChain(void)
- {
- m_vDelegates.clear();
- }
- public:
- bool RegisterEvent(F func, void *pParameter)
- {
- bool bRet = false;
- pair<F, void *> pfvPair(func, pParameter);
- vector<pair<F, void *>>::iterator result;
- result = find(m_vDelegates.begin(), m_vDelegates.end(), pfvPair);
- if (result == m_vDelegates.end()) //同一个对象的同一事件不允许多次接入
- {
- m_vDelegates.push_back(pfvPair);
- bRet = true;
- }
- return bRet;
- }
- void UnRegisterEvent(F func, void *pParameter)
- {
- pair<F, void *> pfvPair(func, pParameter);
- vector<pair<F, void *>>::iterator result;
- result = find(m_vDelegates.begin(), m_vDelegates.end(), pfvPair);
- if (result != m_vDelegates.end())
- m_vDelegates.erase(result);
- }
- int GetEventAndParam(F *pFunc, void **ppParameter)
- {
- size_t len = m_vDelegates.size();
- pair<F, void *> pfvPair;
- if (len > 0)
- {
- pfvPair = m_vDelegates[len - 1]; //后入链的先调用
- *pFunc = pfvPair.first;
- *ppParameter = pfvPair.second;
- return (int)len - 1;
- }
- else
- return -1; //返回-1表示没有后续的成员了
- }
- int GetNextEventAndParam(int iHandle, F *pFunc, void **ppParameter)
- {
- size_t len = m_vDelegates.size();
- if ((iHandle > (int)len - 1) || (iHandle <= 0))
- return -1;
- pair<F, void *> pfvPair;
- pfvPair = m_vDelegates[iHandle -1];
- *pFunc = pfvPair.first;
- *ppParameter = pfvPair.second;
- return iHandle -1;
- }
- private:
- vector<pair<F, void *>> m_vDelegates;
- };
- }
委托模板类CDelegateChain使用指向函数的指针作为模板参数,在类中使用一个列表保存这个函数指针参数和接收这个事件的参数,其定义如下:
- vector<pair<F, void *>> m_vDelegates;
同时提供注册事件方法RegisterEvent,把要注册的事件处理函数和要传递的参数组成一个pair,然后进入m_vDelegates列表保存,使用事件处理函数作为参数的UnRegisterEvent方法,来取消事件注册,还提供GetEventAndParam和GetNextEventAndParam方法,事件触发类通过调用这2个方法来查询出事件链中每一个事件处理函数和需要传递的参数。
读者有兴趣可以自己实现对于"+="和"-="操作符的重载,在使用中就可以更像C#的事件方式了。
(2)、事件触发对象类CNotifyClass的类定义如下:
- #include "../Delegate/DelegateChain.h"
- using dpex::CDelegateChain;
- typedef void (*PEVENT_PARAM_NORETURN)(void *, int);
- class CNotifyClass
- {
- public:
- CNotifyClass(void);
- ~CNotifyClass(void);
- public:
- void DoNotifyEventWork(int iArg);
- public:
- CDelegateChain<PEVENT_PARAM_NORETURN> m_pParam_NoReturn_EventHandler;
- };
类实现如下:
- #include "NotifyClass.h"
- CNotifyClass::CNotifyClass(void)
- {
- }
- CNotifyClass::~CNotifyClass(void)
- {
- }
- void
- CNotifyClass::DoNotifyEventWork(int iArg)
- {
- PEVENT_PARAM_NORETURN func;
- void *pParameter;
- int iHandle = m_pParam_NoReturn_EventHandler.GetEventAndParam(&func, &pParameter);
- if ((iHandle >= 0) && (NULL != func))
- func(pParameter, iArg);
- while (iHandle >= 0)
- {
- iHandle = m_pParam_NoReturn_EventHandler.GetNextEventAndParam(iHandle, &func, &pParameter);
- if ((iHandle >= 0) && (NULL != func))
- func(pParameter, iArg);
- }
- }
事件触发类需要定义事件处理函数的声明,并为每一个事件定义委托模板类CDelegateChain类型的成员变量,代码类似如下:
- CDelegateChain<PEVENT_PARAM_NORETURN> m_pParam_NoReturn_EventHandler;
需要触发事件时使用CDelegateChain类的GetEventAndParam和GetNextEventAndParam方法得到事件处理函数及要传递的参数,然后调用事件处理函数,类似如下的代码调用
- PEVENT_PARAM_NORETURN func;
- void *pParameter;
- int iHandle = m_pParam_NoReturn_EventHandler.GetEventAndParam(&func, &pParameter);
- if ((iHandle >= 0) && (NULL != func))
- func(pParameter, iArg);
- while (iHandle >= 0)
- {
- iHandle = m_pParam_NoReturn_EventHandler.GetNextEventAndParam(iHandle, &func, &pParameter);
- if ((iHandle >= 0) && (NULL != func))
- func(pParameter, iArg);
- }
(3)、事件接收对象类或事件处理对象类CRecvEventClassOne的类定义如下:
- #include "NotifyClass.h"
- class CRecvEventClassOne
- {
- public:
- CRecvEventClassOne(CNotifyClass *pncNotify);
- ~CRecvEventClassOne(void);
- public:
- void DoWork(int iArg);
- protected:
- static void OnParamNoReturnEvent(void *pvParam, int iArg);
- protected:
- CNotifyClass *m_pncNotify;
- };
类实现如下:
- #include "RecvEventClassOne.h"
- CRecvEventClassOne::CRecvEventClassOne(CNotifyClass *pncNotify)
- {
- if (NULL == pncNotify)
- return;
- m_pncNotify = pncNotify;
- m_pncNotify->m_pParam_NoReturn_EventHandler.RegisterEvent(OnParamNoReturnEvent, this);
- }
- CRecvEventClassOne::~CRecvEventClassOne(void)
- {
- if (NULL == m_pncNotify)
- return;
- m_pncNotify->m_pParam_NoReturn_EventHandler.UnRegisterEvent(OnParamNoReturnEvent, this);
- m_pncNotify = NULL;
- }
- void
- CRecvEventClassOne::DoWork(int iArg)
- {
- _tprintf(_T("CRecvEventClassOne Init Argument is %d\n"), iArg);
- m_pncNotify->DoNotifyEventWork(iArg);
- }
- void
- CRecvEventClassOne::OnParamNoReturnEvent(void *pvParam, int iArg)
- {
- _tprintf(_T("Run CRecvEventClassOne::OnParamNoReturnEvent, Argument is: %d\n"), iArg);
- //if (pvParam != NULL)
- //{
- // CRecvEventClassOne *p = reinterpret_cast<CRecvEventClassOne *>(pvParam);
- // //Do something
- //}
- }
事件接收类要定义满足事件接收函数声明格式的静态成员方法来接收事件,在注册事件时使用类似如下的方法来注册
- m_pncNotify->m_pParam_NoReturn_EventHandler.RegisterEvent(OnParamNoReturnEvent, this);
m_pncNotify是事件触发类CNotifyClass类实例指针,通过它的成员变量m_pParam_NoReturn_EventHandler的方法来注册,同样使用类似如下代码来取消注册事件
- m_pncNotify->m_pParam_NoReturn_EventHandler.UnRegisterEvent(OnParamNoReturnEvent, this);
这样就可以实现事件的挂接入链,接收事件,然后进行一定的处理了。
当有多个事件接收处理对象对同一个事件链挂接时,由于委托类CDelegateChain的方法GetEventAndParam和GetNextEventAndParam采用先进后出的方法,就是后接入的事件处理函数先被调用,要修改这个调用顺序读者可以自己修改。
(4)、使用的例子及输出
- int _tmain(int argc, _TCHAR* argv[])
- {
- CNotifyClass ncNotify;
- CRecvEventClassOne rec1(&ncNotify);
- CRecvEventClassTwo rec21(&ncNotify, 1);
- CRecvEventClassTwo rec22(&ncNotify, 2);
- CRecvEventClassThree rec3(&ncNotify);
- int iIn;
- iIn = 10;
- _tprintf(_T("DelegateChainEvent test, Init:%d\n"), iIn);
- _tprintf(_T("DelegateChainEvent test, four object receive event\n"));
- rec1.DoWork(iIn);
- _tprintf(_T("DelegateChainEvent test, third object dont receive event\n"));
- rec22.UnRecvEvent();
- rec1.DoWork(iIn);
- TCHAR c;
- _tscanf(_T("%c"), &c);
- return 0;
- }
输出结果为:
- DelegateChainEvent test, Init:10
- DelegateChainEvent test, four object receive event
- CRecvEventClassOne Init Argument is 10
- Run CRecvEventClassThree::OnParamNoReturnEvent, Argument is: 10
- Run CRecvEventClassTwo::OnParamNoReturnEvent, Argument is: 10
- Run CRecvEventClassTwo::OnParamNoReturnEvent, m_iNum is: 2
- Run CRecvEventClassTwo::OnParamNoReturnEvent, Argument is: 10
- Run CRecvEventClassTwo::OnParamNoReturnEvent, m_iNum is: 1
- Run CRecvEventClassOne::OnParamNoReturnEvent, Argument is: 10
- DelegateChainEvent test, third object dont receive event
- CRecvEventClassOne Init Argument is 10
- Run CRecvEventClassThree::OnParamNoReturnEvent, Argument is: 10
- Run CRecvEventClassTwo::OnParamNoReturnEvent, Argument is: 10
- Run CRecvEventClassTwo::OnParamNoReturnEvent, m_iNum is: 1
- Run CRecvEventClassOne::OnParamNoReturnEvent, Argument is: 10
程序中接入到事件触发类的事件接收类的对象顺序为CRecvEventClassOne对象、内部实例变量为1的CRecvEventClassTwo对象、内部实例变量为2的CRecvEventClassTwo对象、CRecvEventClassThree对象,事件调用的顺序正好反过来调用,调用顺序变成CRecvEventClassThree对象的事件、内部实例变量为2的CRecvEventClassTwo对象的事件、内部实例变量为1的CRecvEventClassTwo对象的事件、CRecvEventClassOne对象的事件,后面让内部实例变量为2的CRecvEventClassTwo对象不再接收这个事件,也就是从事件链中反注册不接收事件,再次执行工作,发现按照顺序CRecvEventClassThree对象的事件、内部实例变量为1的CRecvEventClassTwo对象的事件、CRecvEventClassOne对象的事件接到了事件通知。
2、 实现的要点
(1)、委托类的实现要点
a、 使用模板类的方法定义类,同时把要定义成事件的函数声明作为模板参数
b、 定义事件函数和要传递参数的pair作为元素的列表对象为成员变量
c、 定义注册与反注册事件的方法,支持事件的挂接与取消,方便事件接收类调用来注册事件
d、 定义方法来获取列表对象中每一个事件处理函数和要传递的参数,方便事件触发类调用来获取
这个委托模板类是一次性的,定义完成可以多次复用
(2)、事件触发类的实现要点
a、 事件触发类必须要定义要处理事件的函数声明
b、 事件触发类要使用事件函数声明作为模板参数定义委托类CDelegateChain的成员变量
- CDelegateChain<PEVENT_PARAM_NORETURN> m_pParam_NoReturn_EventHandler;
a、 在工作时,需要触发事件的地方,通过使用CDelegateChain的方法获取每一个事件函数指针和需要传递的参数,然后循环使用传递参数调用事件函数
(3)、事件接收对象类或事件处理对象类的实现要点
a、 使用静态成员方法定义与要接收的事件函数声明相同的方法
b、 使用事件触发类响应事件的委托类成员变量的RegisterEvent和UnRegisterEvent方法注册与反注册是否接收事件,在注册事件时根据需要传递自身this来作为参数
c、 在事件处理的静态方法中,根据需要转换参数为当前对象,然后进行工作
3、 优缺点
(1)、优点
a、可以根据需要选择需要接收的事件
b、事件处理方法不需要必须是public的方法,任意访问类别都可以
c、可以让不同的对象接收同一个事件触发类(服务类)的不同事件
d、委托模板类一次性开发后,直接使用,在事件触发类和事件接收类中的代码简洁,代码量少,容易理解,不容易出错。
e、可以让多个事件接收对象接收同一个事件触发类(服务类)的同一个事件
(2)、缺点
a、针对事件接收类对象的指针参数仍然被转化为void *,相应的其类型安全性相对差些,但是对于事件处理函数的指针因为是模板类参数,所以安全性没有问题,可以在编译期间被检查
b、这里的事件处理函数的声明类型,不能带有返回值,包括通过形参来返回值的情况
最后我们针对这4种比较可行的模拟事件方法进行一下评价,评价的标准主要考虑以下几个方面
1、 是否可以只接收事件触发类的一部分事件
2、 一个事件触发类是否支持多个事件接收类对象接收事件
3、 事件处理函数是否可以使用保护或私有的访问权限,保证事件接收类的封装特性
4、 书写方便程度与代码优雅度、简洁程度,对于这个的标准主要是看开发人员开发事件触发类(服务类)和事件接收类的代码简洁程度,不看委托类的,因为委托类一次开发完成是多次复用的,就如同C#的delegate关键字实际上是编译器使用delegate类展开实现的,我想没有几个人会去把它展开后再说是不是代码太复杂了
5、 类型转换安全程度
6、 是否支持多个对象同时接收一个事件触发类的同一个事件
7、 事件处理函数的格式是否可以任意类型
根据以上7点评价标准,可以列出如下的评价列表
方法 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
静态函数模拟事件 | 可以 | 差 | 可以 | 一般 | 一般 | 不 | 可以 |
接口模拟事件 | 不可以 | 非常差 | 不可以 | 好 | 非常好 | 不 | 可以 |
静态函数与类模板结合模拟事件 | 可以 | 好 | 可以 | 好 | 一般 | 不 | 可以 |
事件链模拟事件 | 可以 | 好 | 可以 | 好 | 一般 | 支持 | 不可以 |
注:5类型转安全程度中,对于接口方式不存在类型转换,所以非常好,对于静态函数及委托模板类的方式针对要传递的参数有类型转换,但是和这个传递参数有关的内容都是一个类来访问,所以安全性一般,如果不使用模板方式处理事件函数指针,事件函数指针也要转换类型,因为要有多个类来访问转换事件函数指针,所以安全性就非常差。
通过以上的评价比较,总结一下各种方法的使用场合
1、 静态函数模拟事件方法通常是在小的项目中,或者测试的程序中使用,灵活容易改变
2、 接口模拟事件方法通常情况下是事件触发类和事件接收类都是一个开发人员或者一个交流很好的团队开发情况下,并且一个接口所有的事件都需要接收事件类实现的时候使用
3、 静态函数与类模板结合模拟事件方法通常是对于比较大的项目,多人开发的情况下,规范整个委托事件体系的情况下来使用,其灵活性和易用性都很高,稳定性也好
4、 事件链模拟事件方法是有特殊事件链需求的时候使用,灵活性和易用性也都很高,主要的限制就在于事件不允许带有返回值,因为事件链的关系,其应用范围还是很广,大型项目也非常适用
总之,正式的项目应该是以结合使用后两种模式为主,根据情况辅助选择前两种模式,应该说第三种方法完全可以替代第一种方法,第一种对于C++程序员唯一的好处就是比较熟悉,容易理解,但是真正的效果还是跟后两种有差距。
根据以上的评价及总结,读者也可以根据需要自行选择某种方法来实现。
最后说明一点,本文的内容及例子都没有考虑多线程的情况,读者如果有兴趣可以根据需要自己设计支持多线程的版本。
在完成这个系列的文章以后,又看了网上的一些相关文章,主要有两方面可以继续变化
1、把所有事件函数的参数使用class或struct进行封装,这个类或结构体作为模板类的新增加的模板参数,这样事件触发类可以不用获取具体的模板类的事件函数指针或对象触发事件,而是通过模板类的统一方法Invoke直接触发事件,但是这个做法需要事件触发类为每个事件额外定义事件函数的参数的class或struct类型,当事件增多,参数列表变的庞大,这种方式也会为编写事件触发类的开发人员增加不少的工作量,代码简洁程度降低不少,当然事件数量少的时候,这种方式看起来更优雅一些,毕竟不存在了类似如下的代码
- m_pNoParam_NoReturn_EventHandler.GetEventAndParam(&func1, &pParameter);
- if (func1 != NULL)
- func1(pParameter);
看起来更友好了,即系列文章中的方法声明定义少,调用复杂一些,封装事件参数法,定义变得复杂,调用简单,所以这个修改根据具体情况见人见智了。
2、使用接口与模板类结合的方法,这个方法仍然可以实现多对象接收不同事件,也可以实现多对象接收同一事件,这种做法就是要触发事件类为每一个事件函数定义一个接口,模板类的模板函数参数使用接口的函数作为参数,触发事件类调用的时候直接获得接口,然后调用,事件接收类需要选择多重继承这些接口并实现,然后就会被事件通知到,这种做法相对于系列文章中有两个好处,安全性非常好,没有数据转换,而且事件接收类不需要定义静态成员方法来接收事件,只需要实例成员方法,也就不存在了类型转换,但是这种方法对于事件触发类增加了比较多的声明代码(每个事件都需要定义一个接口),事件接收类需要继承很多接口,正是主要因为这点不太建议使用,继承太多,继承列表太长,代码不好看,另外多态方式下的性能也有影响,系列文章中分析过对于事件接收类对象的类型转换相对还是可以接受的,就像CreateThread中的那个参数一样,问题不是非常大,感觉为了这个类型转换安全度的提高,需要定义那么多接口,并继承使用,不是非常值得。