博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
在C++中模拟委托事件的方法(四)
阅读量:2180 次
发布时间:2019-05-01

本文共 15141 字,大约阅读时间需要 50 分钟。

转自

三、静态函数方式实现多对象事件接收

对应的例子工程名MultiObjectEvent

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

  1. typedef void (*PEVENT_NOPARAM_NORETURN)(void *); 
  2. typedef int (*PEVENT_NOPARAM_RETURN)(void *); 
  3. typedef void (*PEVENT_PARAM_NORETURN)(void *, int); 
  4. typedef int (*PEVENT_PARAM_RETURN)(void *, int); 
  5.  
  6. class CNotifyClass 
  7. public
  8.     CNotifyClass(void); 
  9.     ~CNotifyClass(void); 
  10.  
  11. public
  12.     bool RegisterEvent(PEVENT_NOPARAM_NORETURN pFunc, void *pParameter); 
  13.     void UnRegisterNoParamNoRetEvent(); 
  14.     bool RegisterEvent(PEVENT_NOPARAM_RETURN pFunc, void *pParameter); 
  15.     void UnRegisterNoParamRetEvent(); 
  16.     bool RegisterEvent(PEVENT_PARAM_NORETURN pFunc, void *pParameter); 
  17.     void UnRegisterParamNoRetEvent(); 
  18.     bool RegisterEvent(PEVENT_PARAM_RETURN pFunc, void *pParameter); 
  19.     void UnRegisterParamRetEvent(); 
  20.  
  21.     void UnRegisterAllEvent(); 
  22.  
  23.     int DoNotifyEventWork(); 
  24.  
  25. protected
  26.     PEVENT_NOPARAM_NORETURN m_pNoParam_NoReturn_EventHandler; 
  27.     PEVENT_NOPARAM_RETURN m_pNoParam_Return_EventHandler; 
  28.     PEVENT_PARAM_NORETURN m_pParam_NoReturn_EventHandler; 
  29.     PEVENT_PARAM_RETURN m_pParam_Return_EventHandler; 
  30.  
  31. protected
  32.     void *m_pNoParamNoRetEventParameter; 
  33.     void *m_pNoParamRetEventParameter; 
  34.     void *m_pParamNoRetEventParameter; 
  35.     void *m_pParamRetEventParameter; 
  36. }; 

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

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

对应的例子工程名DelegateEvent

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

1、  具体的实现方法

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

  1. namespace dpex 
  2.     template <class F> 
  3.     class CDelegate 
  4.     { 
  5.     public
  6.         CDelegate(void
  7.             : m_pParameter(NULL), m_Func(NULL) 
  8.         { 
  9.         } 
  10.  
  11.         ~CDelegate(void
  12.         { 
  13.             UnRegisterEvent(); 
  14.         } 
  15.  
  16.     public
  17.         bool RegisterEvent(F func, void *pParameter) 
  18.         { 
  19.             if (NULL != m_Func) 
  20.                 return false
  21.             m_Func = func; 
  22.             m_pParameter = pParameter; 
  23.             return true
  24.         } 
  25.  
  26.         void UnRegisterEvent() 
  27.         { 
  28.             m_pParameter = NULL; 
  29.             m_Func = NULL; 
  30.         } 
  31.  
  32.         void GetEventAndParam(F *pFunc, void **ppParameter) 
  33.         { 
  34.             *pFunc = m_Func; 
  35.             *ppParameter = m_pParameter; 
  36.         } 
  37.  
  38.     private
  39.         void *m_pParameter; 
  40.         F m_Func; 
  41.     }; 

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

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

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

  1. #include "../Delegate/Delegate.h" 
  2.  
  3. using dpex::CDelegate; 
  4.  
  5. typedef void (*PEVENT_NOPARAM_NORETURN)(void *); 
  6. typedef int (*PEVENT_NOPARAM_RETURN)(void *); 
  7. typedef void (*PEVENT_PARAM_NORETURN)(void *, int); 
  8. typedef int (*PEVENT_PARAM_RETURN)(void *, int); 
  9.  
  10. class CNotifyClass 
  11. public
  12.     CNotifyClass(void); 
  13.     ~CNotifyClass(void); 
  14.  
  15. public
  16.     int DoNotifyEventWork(); 
  17.  
  18. public
  19.     CDelegate<PEVENT_NOPARAM_NORETURN> m_pNoParam_NoReturn_EventHandler; 
  20.     CDelegate<PEVENT_NOPARAM_RETURN> m_pNoParam_Return_EventHandler; 
  21.     CDelegate<PEVENT_PARAM_NORETURN> m_pParam_NoReturn_EventHandler; 
  22.     CDelegate<PEVENT_PARAM_RETURN> m_pParam_Return_EventHandler; 
  23. }; 

类实现如下:

  1. #include "NotifyClass.h" 
  2.  
  3. CNotifyClass::CNotifyClass(void
  4.  
  5. CNotifyClass::~CNotifyClass(void
  6.     m_pNoParam_NoReturn_EventHandler.UnRegisterEvent(); 
  7.     m_pNoParam_Return_EventHandler.UnRegisterEvent(); 
  8.     m_pParam_NoReturn_EventHandler.UnRegisterEvent(); 
  9.     m_pParam_Return_EventHandler.UnRegisterEvent(); 
  10.  
  11. int 
  12. CNotifyClass::DoNotifyEventWork() 
  13.     int iResult = 0; 
  14.     PEVENT_NOPARAM_NORETURN func1; 
  15.     PEVENT_NOPARAM_RETURN func2; 
  16.     PEVENT_PARAM_NORETURN func3; 
  17.     PEVENT_PARAM_RETURN func4; 
  18.     void *pParameter; 
  19.  
  20.     m_pNoParam_NoReturn_EventHandler.GetEventAndParam(&func1, &pParameter); 
  21.     if (func1 != NULL) 
  22.         func1(pParameter); 
  23.     m_pNoParam_Return_EventHandler.GetEventAndParam(&func2, &pParameter); 
  24.     if (func2 != NULL) 
  25.         iResult = func2(pParameter); 
  26.  
  27.     iResult = iResult + 10; 
  28.     m_pParam_NoReturn_EventHandler.GetEventAndParam(&func3, &pParameter); 
  29.     if (func3 != NULL) 
  30.         func3(pParameter, iResult); 
  31.     iResult = iResult + 10; 
  32.     m_pParam_Return_EventHandler.GetEventAndParam(&func4, &pParameter); 
  33.     if (func4 != NULL) 
  34.         iResult = func4(pParameter, iResult); 
  35.  
  36.     return iResult; 

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

  1. CDelegate<PEVENT_NOPARAM_NORETURN> m_pNoParam_NoReturn_EventHandler; 

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

  1. PEVENT_NOPARAM_NORETURN func1; 
  2. void *pParameter; 
  3. m_pNoParam_NoReturn_EventHandler.GetEventAndParam(&func1, &pParameter); 
  4. if (func1 != NULL) 
  5.     func1(pParameter); 

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

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

  1. #include "NotifyClass.h" 
  2.  
  3. class CRecvEventClassOne 
  4. public
  5.     CRecvEventClassOne(CNotifyClass *pncNotify); 
  6.     ~CRecvEventClassOne(void); 
  7.  
  8. public
  9.     int DoWork(int iArg); 
  10.  
  11. protected
  12.     static void OnNoParamNoReturnEvent(void *pvParam); 
  13.     static int OnNoParamReturnEvent(void *pvParam); 
  14.  
  15. protected
  16.     CNotifyClass *m_pncNotify; 
  17.     int m_nNum; 
  18. }; 

类实现如下:

  1. #include "RecvEventClassOne.h" 
  2.  
  3. CRecvEventClassOne::CRecvEventClassOne(CNotifyClass *pncNotify) 
  4.     if (NULL == pncNotify) 
  5.         return
  6.     m_pncNotify = pncNotify; 
  7.     m_pncNotify->m_pNoParam_NoReturn_EventHandler.RegisterEvent(OnNoParamNoReturnEvent, this); 
  8.     m_pncNotify->m_pNoParam_Return_EventHandler.RegisterEvent(OnNoParamReturnEvent, this); 
  9.  
  10. CRecvEventClassOne::~CRecvEventClassOne(void
  11.     if (NULL == m_pncNotify) 
  12.         return
  13.     m_pncNotify->m_pNoParam_NoReturn_EventHandler.UnRegisterEvent(); 
  14.     m_pncNotify->m_pNoParam_Return_EventHandler.UnRegisterEvent(); 
  15.     m_pncNotify = NULL; 
  16.  
  17. int 
  18. CRecvEventClassOne::DoWork(int iArg) 
  19.     int iRet; 
  20.     m_nNum = iArg; 
  21.     _tprintf(_T("CRecvEventClassOne m_num is %d\n"), m_nNum); 
  22.     iRet = m_pncNotify->DoNotifyEventWork(); 
  23.     return iRet; 
  24.  
  25. void 
  26. CRecvEventClassOne::OnNoParamNoReturnEvent(void *pvParam) 
  27.     _tprintf(_T("Run CRecvEventClassOne::OnNoParamNoReturnEvent\n")); 
  28.     if (pvParam != NULL) 
  29.     { 
  30.         CRecvEventClassOne *p = reinterpret_cast<CRecvEventClassOne *>(pvParam); 
  31.         p->m_nNum = p->m_nNum + 10; 
  32.         _tprintf(_T("CRecvEventClassOne m_num is %d\n"), p->m_nNum); 
  33.     } 
  34.  
  35. int 
  36. CRecvEventClassOne::OnNoParamReturnEvent(void *pvParam) 
  37.     _tprintf(_T("Run CRecvEventClassOne::OnNoParamReturnEvent\n")); 
  38.     if (pvParam != NULL) 
  39.     { 
  40.         CRecvEventClassOne *p = reinterpret_cast<CRecvEventClassOne *>(pvParam); 
  41.         p->m_nNum = p->m_nNum + 10; 
  42.         _tprintf(_T("CRecvEventClassOne m_num is %d\n"), p->m_nNum); 
  43.         return p->m_nNum; 
  44.     } 
  45.     else 
  46.         return 0; 

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

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

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

  1. m_pncNotify->m_pNoParam_NoReturn_EventHandler.UnRegisterEvent(); 

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

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

  1. int _tmain(int argc, _TCHAR* argv[]) 
  2.     CNotifyClass ncNotify; 
  3.     CRecvEventClassOne rec1(&ncNotify); 
  4.     CRecvEventClassTwo rec2(&ncNotify); 
  5.     int iIn, iOut; 
  6.  
  7.     iIn = 10; 
  8.     iOut = rec1.DoWork(iIn); 
  9.     _tprintf(_T("DelegateEvent test, Init:%d, Result:%d\n"), iIn, iOut); 
  10.  
  11.     TCHAR c; 
  12.     _tscanf(_T("%c"), &c); 
  13.     return 0; 

输出结果为:

  1. CRecvEventClassOne m_num is 10 
  2. Run CRecvEventClassOne::OnNoParamNoReturnEvent 
  3. CRecvEventClassOne m_num is 20 
  4. Run CRecvEventClassOne::OnNoParamReturnEvent 
  5. CRecvEventClassOne m_num is 30 
  6. Run CRecvEventClassTwo::OnParamNoReturnEvent 
  7. CRecvEventClassTwo m_num is 50 
  8. Run CRecvEventClassTwo::OnParamReturnEvent 
  9. CRecvEventClassTwo m_num is 110 
  10. DelegateEvent test, Init:10, Result:110 

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

2、  实现的要点

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

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

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

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

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

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

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

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

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

  1. 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 *,虽然可行,但是传递接入是事件接收类,进行类型转换,并进行调用的是事件触发类,所以这里关系到两个类,定义与使用的类不同,这种情况下出问题的可能性大大增加,所以要使用模板类来处理这个问题,避免这种危险的类型转换。

 

转载地址:http://ckskb.baihongyu.com/

你可能感兴趣的文章
初探Java设计模式4:一文带你掌握JDK中的设计模式
查看>>
初探Java设计模式5:一文了解Spring涉及到的9种设计模式
查看>>
Java集合详解1:一文读懂ArrayList,Vector与Stack使用方法和实现原理
查看>>
Java集合详解2:一文读懂Queue和LinkedList
查看>>
Java集合详解3:一文读懂Iterator,fail-fast机制与比较器
查看>>
Java集合详解4:一文读懂HashMap和HashTable的区别以及常见面试题
查看>>
Java集合详解5:深入理解LinkedHashMap和LRU缓存
查看>>
Java集合详解6:这次,从头到尾带你解读Java中的红黑树
查看>>
Java集合详解7:一文搞清楚HashSet,TreeSet与LinkedHashSet的异同
查看>>
Java集合详解8:Java集合类细节精讲,细节决定成败
查看>>
Java并发指南1:并发基础与Java多线程
查看>>
Java并发指南2:深入理解Java内存模型JMM
查看>>
Java并发指南3:并发三大问题与volatile关键字,CAS操作
查看>>
Java并发指南4:Java中的锁 Lock和synchronized
查看>>
Java并发指南5:JMM中的final关键字解析
查看>>
Java并发指南6:Java内存模型JMM总结
查看>>
Java并发指南7:JUC的核心类AQS详解
查看>>
Java并发指南8:AQS中的公平锁与非公平锁,Condtion
查看>>
Java网络编程和NIO详解6:Linux epoll实现原理详解
查看>>
Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理
查看>>