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. css3实现立方体,并且自转效果

    先是HTML 一个父div包含四个绝对定位的div <div class='container container--realistic'> <div class='cube cub ...

  2. JavaScript中变量、作用域、内存问题

    这几天,闲的没事看看JavaScript高级编程,感觉JavaScript真的很强大,尤其是采用面向对象的编程方式. 一.   基本类型和引用类型的值: ECMAScript变量可能包含两种不同数据类 ...

  3. XML DOM(Document Object Model)

    1.XML DOM 是用于获取.更改.添加或删除 XML 元素的标准.2.节点(XML 文档中的每个成分都是一个节点):        整个文档是一个文档节点:        每个XML元素是一个元素 ...

  4. Unity3D 向量运算

    写在前面的话,前两天有个朋友在QQ上问我 如何获取主角面朝方向一定区域中的敌人对象.这个命题看似简单,其实里面蕴含了很多数学方面的东西.今天刚好有时间我就彻底的把这个疑问写在博客中.希望可以帮助到他. ...

  5. 【WPF】RenderTransform和LayoutTransform

    布局系统 在WPF中,许多绘图任务通过使用变换(transform)可以变得更加简单——变换是通过不加通告地切换形状或元素使用的坐标系统来改变形状或元素绘制方式的对象.在WPF中,变换的一些类大多继承 ...

  6. 「BZOJ 2534」 L - gap字符串

    「BZOJ 2534」 L - gap字符串 题目描述 有一种形如 \(uv u\) 形式的字符串,其中 \(u\) 是非空字符串,且 \(v\) 的长度正好为 \(L\), 那么称这个字符串为 \( ...

  7. Effective Java部分读书笔记

    2.创建和销毁对象 1.使用静态工厂方法代替构造器 一般使用构造器(构造函数)创建对象实例,还可以使用静态工厂方法来创建对象实例. 优点 使用静态工厂方法代替构造器创建对象实例有以下优点: 1)静态构 ...

  8. DP练习 最长上升子序列nlogn解法

    openjudge 百练 2757:最长上升子序列 总时间限制:  2000ms 内存限制:  65536kB 描述 一个数的序列bi,当b1 < b2 < ... < bS的时候, ...

  9. MySQL5.7添加授权账号及修改默认端口

    1.修改默认端口 打开配置文件 vim /etc/my.cnf 分别添加端口在client.mysql节点 [client] port=15099 [mysqld] port=15099 需要注意se ...

  10. CSS的outline属性

    input标签的outline的三个属性: outline-color outline-style outline-width 当设置input的focus状态的时候可以使用. input:focus ...