C++实现委托机制(一)

1.引言:

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

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

2.委托功能使用:

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

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

[cpp] view plain copy

  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++中普通函数指针和成员函数指针的区别:

[cpp] view plain copy

  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. }

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

[cpp] view plain copy

  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,动态类型的判断。

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

[cpp] view plain copy

  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又是不确定的所以我们这里要使用模板类来封装:

[cpp] view plain copy

  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++中的无返回值、无参数的普通函数指针、成员函数指针封装好了。

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

[cpp] view plain copy

  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>

[cpp] view plain copy

  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. };

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

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

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

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

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

[cpp] view plain copy

  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++实现委托机制(二)

时间: 2024-10-24 16:28:00

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

类加载的父亲委托机制

我们都知道.类加载器用来把类加载到java虚拟机.从JDK2.0开始,类的加载过程采用父亲委托机制.JVM的ClassLoader采用的是树形结构,除了根类加载器以外,每个ClassLoader都会有且仅有一个父类加载器,用户自定义的ClassLoader默认的父类加载器是系统类加载器,当然你可以自己指定需要用个ClassLoader的实例,我们来看他们的父子关系: 父类委托机制中,当一个java程序请求加载器loader1加载Hello类时,loader1首先委托自己的父亲加载器加载hello

在Unity中使用事件/委托机制(event/delegate)进行GameObject之

欢迎来到unity学习.unity培训.unity企业培训教育专区,这里有很多U3D资源.U3D培训视频.U3D教程.U3D常见问题.U3D项目源码,[狗刨学习网]unity极致学院,致力于打造业内unity3d培训.学习第一品牌. 一对多的观察者模式机制有什么缺点? 如果你对如何在Unity中使用事件/委托机制还不太了解,建议您查看我的前一篇文章:[Unity3D技巧]在Unity中使用事件/委托机制(event/delegate)进行GameObject之间的通信 在前一篇博客里面,我们写到

JQuery中的事件委托机制:delegate和undelegate

考虑下面这种场景:如果1个div下面有3个button,点击每个按钮的时候,需要打印出当前按钮的ID. <div id="parent"> <input type="button" id="a" value="1"></input> <input type="button" id="b" value="2"></i

java类加载器和父类委托机制

1.类加载器 Java虚拟机中可以安装多个类加载器,系统默认主要三个类加载器,每个类负责加载特定位置的类:BootStrap(内嵌在java虚拟机中由C++编写),ExtClassLoader,AppClassLoader.当然也可以自定义类加载器,自定义的加载器必须继承ClassLoader. Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父级类加载器对象或者默认采用系统类加载器为其父级类加载. 2.下面我们来看如下一段代码: p

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

Java虚拟机JVM学习05 类加载器的父委托机制 类加载器 类加载器用来把类加载到Java虚拟机中. 类加载器的类型 有两种类型的类加载器: 1.JVM自带的加载器: 根类加载器(Bootstrap) 扩展类加载器(Extension) 系统类加载器(System) 2.用户自定义的类加载器: java.lang.ClassLoader的子类,用户可以定制类的加载方式. JVM自带的加载器 Java虚拟机自带了以下几种加载器. 1.根(Bootstrap)类加载器: 该加载器没有父加载器. 它

JAVA类加载器一 父类委托机制

类加载器负责将.class文件加载到内存中,并为之生成对应的Class对象.其中class文件有可能存在磁盘上,也有可能存在网络上. 当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构: 类加载机制:全盘负责和父类委托机制 全盘负责:就是当一个classloader加载一个Class的时候,这个class所依赖的和医用的其他class对象通常也由这个classloader负责载入. 委托机制:先让父类加载器寻找,只有在父类加载器找不到的情况下才从自己的类路径中去寻找. 类加载还采用了

【Unity3D技巧】在Unity中使用事件/委托机制(event/delegate)进行GameObject之间的通信

作者:王选易,出处:http://www.cnblogs.com/neverdie/ 欢迎转载,也请保留这段声明.如果你喜欢这篇文章,请点[推荐].谢谢! 引子 在前面两篇文章: [Unity3D基础教程]给初学者看的Unity教程(四):通过制作Flappy Bird了解Native 2D中的RigidBody2D和Collider2D [Unity3D基础教程]给初学者看的Unity教程(三):通过制作Flappy Bird了解Native 2D中的Sprite,Animation 我们了解

在Unity中使用事件/委托机制(event/delegate)进行GameObject之间的通信

欢迎来到unity学习.unity培训.unity企业培训教育专区,这里有很多U3D资源.U3D培训视频.U3D教程.U3D常见问题.U3D项目源码,[狗刨学习网]unity极致学院,致力于打造业内unity3d培训.学习第一品牌. 引子 在前面两篇文章: 我们了解了2D中的Sprite,Animation,RigidBody和Collider,在继续开发游戏的过程中,我们会遇到这样的问题,如何处理GameObject之间的相互调用,比如说在FlappyBird中我们在小鸟撞倒管子的时候,要把这

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

一.自定义类加载器的一般步骤 Java的类加载器自从JDK1.2开始便引入了一条机制叫做父类委托机制.一个类需要被加载的时候,JVM先会调用他的父类加载器进行加载,父类调用父类的父类,一直到顶级类加载器.如果父类加载器加载不了,依次再使用其子类进行加载.当然这类所说的父类加载器,不一定他们之间是继承的关系,有可能仅仅是包装的关系. Java之所以出现这条机制,因为是处于安全性考虑.害怕用户自己定义class文件然后自己写一个类加载器来加载原本应该是JVM自己加载的类.这样会是JVM虚拟机混乱或者