1.引言:

如果你接触过C#,你就会觉得C#中的delegate(委托)十分灵巧,它的用法上和C\C++的函数指针很像,但是却又比C\C++的函数指针更加灵活。并且委托可以一对多,也就是可以注册多个函数,甚至是某个类的非静态成员函数。而实现事件消息机制【1】也十分依赖于委托机制。基于这样的目的,我们试着在C++上封装出这样的一个委托机制。

【1】值得注意的是这里的委托事件模式与Windows的消息循环体系是不同的,通常Windows的消息是放到消息队列中,应用程序进程从队列中得到消息,然后调用消息处理过程来处理消息,这里是真正的消息通知,并且消息处理过程是有固定的函数声明的,不能更改成其他的格式,但是委托事件模式实际上就是一次函数调用,委托事件模式的使用,其好处是在开发中可以像真正的消息事件体系一样来理解整个体系模式,可以做到很好的接口分离。

2.委托功能使用:

委托使用简单,支持多播,可以添加删除委托。同时支持C++的普通函数、模板函数、类成员函数,类的静态成员函数,并且支持多态。

我们来看一个简单的例子:

  1. #include "MyDelegate.h"
  2. using namespace Delegate;
  3. void NormalFunc(int a)
  4. {
  5. printf("这里是普通函数 :%d\n", a);
  6. }
  7. class A
  8. {
  9. public:
  10. static void StaticFunc(int a)
  11. {
  12. printf("这里是成员静态函数 : %d\n", a);
  13. }
  14. void MemberFunc(int a)
  15. {
  16. printf("这里是成员非静态函数 : %d\n", a);
  17. }
  18. };
  19. int _tmain(int argc, _TCHAR* argv[])
  20. {
  21. //首先创建了一个返回值为 void ,参数为int 的一个委托。
  22. CMultiDelegate<void, int> e;
  23. //将三个函数注册到该委托中
  24. e += newDelegate(NormalFunc);
  25. e += newDelegate(A::StaticFunc);
  26. e += newDelegate(&A(), &A::MemberFunc);
  27. //调用
  28. e(1);
  29. return 0;
  30. }

运行结果:

这里是普通函数 :1
这里是成员静态函数 : 1
这里是成员非静态函数 : 1

由此可以看到将三个函数注册到委托中后,调用委托不仅三个函数不仅能够成功调用,而且参数也是成功传递的。

3.实现无返回值无参数委托的构造


         这一部分代码是参照http://blog.csdn.NET/gouki04/article/details/6852394这篇博客上写的。

我们先来看C++中普通函数指针和成员函数指针的区别:

  1. void NormalFunc()
  2. {
  3. printf("这里是普通函数\n");
  4. }
  5. class A
  6. {
  7. public:
  8. static void StaticFunc()
  9. {
  10. printf("这里是成员静态函数\n");
  11. }
  12. void MemberFunc()
  13. {
  14. printf("这里是成员非静态函数\n");
  15. }
  16. };
  17. int _tmain(int argc, _TCHAR* argv[])
  18. {
  19. //普通函数
  20. typedef void(*NormalFuncp)();
  21. //成员函数
  22. typedef void(A::*MemberFuncp)();
  23. NormalFuncp fun1 = NormalFunc;
  24. MemberFuncp fun2 = &A::MemberFunc;
  25. NormalFuncp fun3 = A::StaticFunc;
  26. A a;
  27. fun1();
  28. (a.*fun2)();
  29. fun3();
  30. return 0;
  31. }

可以看到普通函数指针调用函数的方式和成员非静态函数指针调用函数的方式不同,成员非静态函数指针调用函数需要依赖于该类的一个对象,并且用 .* 或者 ->* 的语法来调用。而成员静态函数调用方式却和普通函数差不多。所以我们需要创建一个委托的基本接口对于不同类型指针的再来派生多态处理。

  1. class IDelegate
  2. {
  3. public:
  4. virtual ~IDelegate() { }
  5. virtual bool isType(const std::type_info& _type) = 0;
  6. virtual void invoke() = 0;
  7. virtual bool compare(IDelegate *_delegate) const = 0;
  8. };

这里定义了三个接口,一个是调用,表示调用该Delegate对应的函数指针指向的函数。剩下两个是类型判断,使用了C++的RTTI,动态类型的判断。

接下来我们来派生出能注册普通函数的委托。

  1. class CStaticDelegate : public IDelegate
  2. {
  3. public:
  4. typedef void (*Func)();
  5. CStaticDelegate(Func _func) : mFunc(_func) { }
  6. virtual bool isType(const std::type_info& _type) { return typeid(CStaticDelegate) == _type; }
  7. virtual void invoke() { mFunc(); }
  8. virtual bool compare(IDelegate *_delegate) const
  9. {
  10. if (0 == _delegate || !_delegate->isType(typeid(CStaticDelegate)) ) return false;
  11. CStaticDelegate * cast = static_cast<CStaticDelegate*>(_delegate);
  12. return cast->mFunc == mFunc;
  13. }
  14. private:
  15. Func mFunc;
  16. };

然后是可以注册指向成员非静态函数的指针的委托,因为指向成员非静态函数的类别是这样的  void (ClassName::*FuncName)();而ClassName又是不确定的所以我们这里要使用模板类来封装:

  1. template<class T>
  2. class CMethodDelegate : public IDelegate
  3. {
  4. public:
  5. typedef void (T::*Method)();
  6. CMethodDelegate(T * _object, Method _method) : mObject(_object), mMethod(_method) { }
  7. virtual bool isType( const std::type_info& _type) { return typeid(CMethodDelegate<T>) == _type; }
  8. virtual void invoke()
  9. {
  10. (mObject->*mMethod)();
  11. }
  12. virtual bool compare(IDelegate *_delegate) const
  13. {
  14. if (0 == _delegate || !_delegate->isType(typeid(CMethodDelegate<T>))) return false;
  15. CMethodDelegate<T>* cast = static_cast<CMethodDelegate<T>*>(_delegate);
  16. return cast->mObject == mObject && cast->mMethod == mMethod;
  17. }
  18. private:
  19. T * mObject;
  20. Method mMethod;
  21. };

这里的类型T是指这个委托注册的成员函数指针所属的类的类别。比如我注册 A::&MemberFunc ,那么这里的T就被替换为A.

其实大家仔细看代码可以发现这两个类十分相似只是invoke() 里面调用的方式不同。还有这里的compare判断是指看两个委托指向的成员函数和对象是否一样,如果只是成员函数一样,绑定的对象不一样也视作不同的委托。

这样我们就把C++中的无返回值、无参数的普通函数指针、成员函数指针封装好了。

最后提供统一的接口去生成”函数指针对象

  1. inline IDelegate* newDelegate( void (*_func)() )
  2. {
  3. return new CStaticDelegate(_func);
  4. }
  5. template<class T>
  6. inline IDelegate* newDelegate( T * _object, void (T::*_method)() )
  7. {
  8. return new CMethodDelegate<T>(_object, _method);
  9. }

最后我们我们实现委托,这里我们对多个函数指针的存储使用了STL的list.所以头文件中需要引入<list>

  1. class CMultiDelegate
  2. {
  3. public:
  4. typedef std::list<IDelegate*> ListDelegate;
  5. typedef ListDelegate::iterator ListDelegateIterator;
  6. typedef ListDelegate::const_iterator ConstListDelegateIterator;
  7. CMultiDelegate () { }
  8. ~CMultiDelegate () { clear(); }
  9. bool empty() const
  10. {
  11. for (ConstListDelegateIterator iter = mListDelegates.begin(); iter!=mListDelegates.end(); ++iter)
  12. {
  13. if (*iter) return false;
  14. }
  15. return true;
  16. }
  17. void clear()
  18. {
  19. for (ListDelegateIterator iter=mListDelegates.begin(); iter!=mListDelegates.end(); ++iter)
  20. {
  21. if (*iter)
  22. {
  23. delete (*iter);
  24. (*iter) = 0;
  25. }
  26. }
  27. }
  28. CMultiDelegate& operator+=(IDelegate* _delegate)
  29. {
  30. for (ListDelegateIterator iter=mListDelegates.begin(); iter!=mListDelegates.end(); ++iter)
  31. {
  32. if ((*iter) && (*iter)->compare(_delegate))
  33. {
  34. delete _delegate;
  35. return *this;
  36. }
  37. }
  38. mListDelegates.push_back(_delegate);
  39. return *this;
  40. }
  41. CMultiDelegate& operator-=(IDelegate* _delegate)
  42. {
  43. for (ListDelegateIterator iter=mListDelegates.begin(); iter!=mListDelegates.end(); ++iter)
  44. {
  45. if ((*iter) && (*iter)->compare(_delegate))
  46. {
  47. if ((*iter) != _delegate) delete (*iter);
  48. (*iter) = 0;
  49. break;
  50. }
  51. }
  52. delete _delegate;
  53. return *this;
  54. }
  55. void operator()( )
  56. {
  57. ListDelegateIterator iter = mListDelegates.begin();
  58. while (iter != mListDelegates.end())
  59. {
  60. if (0 == (*iter))
  61. {
  62. iter = mListDelegates.erase(iter);
  63. }
  64. else
  65. {
  66. (*iter)->invoke();
  67. ++iter;
  68. }
  69. }
  70. }
  71. private:
  72. CMultiDelegate (const CMultiDelegate& _event);
  73. CMultiDelegate& operator=(const CMultiDelegate& _event);
  74. private:
  75. ListDelegate mListDelegates;
  76. };

其实最后这个类很像是一个指针容器,然后各个成员方法也只是对这个容器里面的对象进行管理。而主要的三个方法:

重载了 +=  表示向这个委托注册一个函数指针,这个方法会自动判重,如果重复了就不会向里面添加。

重载了  -=  表示向这个委托注销一个函数指针,如果这个函数指针不存在就什么也不执行。

重载了  ()   表示当作函数调用启动这个委托,内部就是将所有函数指针指向的函数都运行一遍。

到这里,基本上无返回值、无参数的委托就封装好了。我们先来测试一下:

  1. void Say()
  2. {
  3. printf("你好\n");
  4. }
  5. class A
  6. {
  7. public :
  8. void Say(){ printf("你不好\n"); }
  9. };
  10. int _tmain(int argc, _TCHAR* argv[])
  11. {
  12. CMultiDelegate onclick;
  13. onclick += newDelegate(Say);
  14. onclick += newDelegate(&A(),&A::Say);   //注意这里不能传入 new A(), 因为会内存泄漏。
  15. onclick();

如果以上代码能够成功运行,那么说明你的第一个版本的委托已经封装完毕,但是如何实现任意返回值、任意参数类型、任意参数个数的函数指针的委托呢?

我在网上查阅过许多代码,发现大多数都是使用的宏替换加上多次引用头文件使得每次编译不同参数个数版本的委托,但是这个方法我感觉巧妙但却鸡肋。后来我尝试着使用C11的新特性:可变模板参数实现了这个需求。能够对用户定义的不同委托去自动生成对应的函数指针类型的委托类。

具体的代码详见 C++实现委托机制(二)

C++实现委托机制(一)的更多相关文章

  1. C++模拟C#事件委托机制(一)

    原文来自于http://www.cnblogs.com/netssfy/articles/1652671.html 写了一段时间的C#代码后确实发现C#的事件委托非常好用.于是便想是否在C++中也能如 ...

  2. Javascript事件模型系列(二)事件的捕获-冒泡机制及事件委托机制

    一.事件的捕获与冒泡 由W3C规定的DOM2标准中,一次事件的完整过程包括三步:捕获→执行目标元素的监听函数→冒泡,在捕获和冒泡阶段,会依次检查途径的每个节点,如果该节点注册了相应的监听函数,则执行监 ...

  3. Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论

    Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论 创建用户自定义的类加载器 要创建用户自定义的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的f ...

  4. Java虚拟机JVM学习05 类加载器的父委托机制

    Java虚拟机JVM学习05 类加载器的父委托机制 类加载器 类加载器用来把类加载到Java虚拟机中. 类加载器的类型 有两种类型的类加载器: 1.JVM自带的加载器: 根类加载器(Bootstrap ...

  5. Invoke() 方法是 Unity3D 的一种委托机制

    Invoke() 方法是 Unity3D 的一种委托机制 如: Invoke("SendMsg", 5);   它的意思是:5 秒之后调用 SendMsg() 方法: 使用 Inv ...

  6. 【Unity3D技巧】在Unity中使用事件/委托机制(event/delegate)进行GameObject之间的通信 (二) : 引入中间层NotificationCenter

    作者:王选易,出处:http://www.cnblogs.com/neverdie/ 欢迎转载,也请保留这段声明.如果你喜欢这篇文章,请点[推荐].谢谢! 一对多的观察者模式机制有什么缺点? 想要查看 ...

  7. java类加载器学习2——自定义类加载器和父类委托机制带来的问题

    一.自定义类加载器的一般步骤 Java的类加载器自从JDK1.2开始便引入了一条机制叫做父类委托机制.一个类需要被加载的时候,JVM先会调用他的父类加载器进行加载,父类调用父类的父类,一直到顶级类加载 ...

  8. Java基础---Java---基础加强---类加载器、委托机制、AOP、 动态代理技术、让动态生成的类成为目标类的代理、实现Spring可配置的AOP框架

    类加载器 Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader 类加载器也是Jav ...

  9. JavaScript 之默认行为 DOM2级,事件委托机制

    1. 事件默认行为及阻止方式    1.1 浏览器的默认行为       JavaScript事件本身所具有的属性,例如a标签的跳转,Submit按钮的提交,右键菜单,文本框的输入等.    1.2 ...

  10. JAVA基础|从Class.forName初始化数据库到SPI破坏双亲委托机制

    代码托管在:https://github.com/fabe2ry/classloaderDemo 初始化数据库 如果你写过操作数据库的程序的话,可能会注意,有的代码会在程序的开头,有Class.for ...

随机推荐

  1. ArrayList to Array Conversion in Java

    ArrayList to Array Conversion in Java Following methods can be used for converting ArrayList to Arra ...

  2. HTML5 canvas绘制arcTo、translate和rotate的画法探索

      arcTo(x1,y1,x2,y2,radius) ; 还要加上moveTo的点(x0,y0) ; 第一步:找到切点 过点 (x1,y1), (x0,y0)引射线与点(x1,y1),(x2,y2) ...

  3. Socket编程(一):建立与客户端的连接并接受数据

    我们这里利用Socket在模拟一个客户端与服务器通信,其实客户端与服务端通信就像人与人打电话一样,想要给一个人打电话,我们首先必须要有手机,必须知道对方的手机号码,这里Socket就好比一部手机,而短 ...

  4. 关于button标签会刷新页面的问题

    当button标签在form表单里面时,这时点击button按钮会提交表单刷新页面. <form action=""> <button>点击</but ...

  5. GETATTR,DELATTR,SETATTR与GETITEM,SETITEM,DELITEM区别

    通过对象.属性的方式触发的是__getattr__,__delattr__,__setattr__ 通过对象['属性']触发__getitem__,__setitem__,__delitem__ cl ...

  6. Django+Nginx+uwsgi搭建自己的博客(五)

    在上一篇博文中,向大家介绍了Users App和Index的前端部分的实现,以及前端与Django的通信部分.至此,我们的博客已经具备一个简单的雏形,可以将其部署在本地的服务器上了.目前较为流行的we ...

  7. FastReport.Net使用:[27]样式使用

    样式设置与使用 1.打开样式设置界面,通过 报表->样式 来打开. 2.样式设置包含:边框,填充,字体和文本颜色.假如不需要某项设置,可将其选择框去掉. 3.设置好样式后,将标题的style设置 ...

  8. 解决关于stack溢出的问题

    开发中经常遇到: 前端遇到Uncaught RangeError: Maximum call stack size exceeded错误 后台遇到java.lang.OutOfMemoryError: ...

  9. [BZOJ4444][SCOI2015]国旗计划(倍增)

    链上是经典贪心问题,将线段全按左端点排序后把点全撒在线段右端点上.这里放到环上,倍长即可. 题目保证不存在区间包含情况,于是有一种暴力做法,先将战士的管辖区间按左端点从小到大排序,对于询问x,从x战士 ...

  10. Test20171009 考试总结 NOIP模拟赛

    题目难度合适,区分度适中,但是本人水平不佳,没有拿到满意的分数. T1(matrix) 一种比较容易想到的想法是枚举起点求出最长全1串做预处理,这是O(n^2)的. 接着枚举列起点,列终点,通过后缀和 ...