C++实现委托机制(一)
1.引言:
如果你接触过C#,你就会觉得C#中的delegate(委托)十分灵巧,它的用法上和C\C++的函数指针很像,但是却又比C\C++的函数指针更加灵活。并且委托可以一对多,也就是可以注册多个函数,甚至是某个类的非静态成员函数。而实现事件消息机制【1】也十分依赖于委托机制。基于这样的目的,我们试着在C++上封装出这样的一个委托机制。
【1】值得注意的是这里的委托事件模式与Windows的消息循环体系是不同的,通常Windows的消息是放到消息队列中,应用程序进程从队列中得到消息,然后调用消息处理过程来处理消息,这里是真正的消息通知,并且消息处理过程是有固定的函数声明的,不能更改成其他的格式,但是委托事件模式实际上就是一次函数调用,委托事件模式的使用,其好处是在开发中可以像真正的消息事件体系一样来理解整个体系模式,可以做到很好的接口分离。
2.委托功能使用:
委托使用简单,支持多播,可以添加删除委托。同时支持C++的普通函数、模板函数、类成员函数,类的静态成员函数,并且支持多态。
我们来看一个简单的例子:
- #include "MyDelegate.h"
- using namespace Delegate;
- void NormalFunc(int a)
- {
- printf("这里是普通函数 :%d\n", a);
- }
- class A
- {
- public:
- static void StaticFunc(int a)
- {
- printf("这里是成员静态函数 : %d\n", a);
- }
- void MemberFunc(int a)
- {
- printf("这里是成员非静态函数 : %d\n", a);
- }
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- //首先创建了一个返回值为 void ,参数为int 的一个委托。
- CMultiDelegate<void, int> e;
- //将三个函数注册到该委托中
- e += newDelegate(NormalFunc);
- e += newDelegate(A::StaticFunc);
- e += newDelegate(&A(), &A::MemberFunc);
- //调用
- e(1);
- return 0;
- }
运行结果:
这里是普通函数 :1
这里是成员静态函数 : 1
这里是成员非静态函数 : 1
由此可以看到将三个函数注册到委托中后,调用委托不仅三个函数不仅能够成功调用,而且参数也是成功传递的。
3.实现无返回值无参数委托的构造
这一部分代码是参照http://blog.csdn.NET/gouki04/article/details/6852394这篇博客上写的。
我们先来看C++中普通函数指针和成员函数指针的区别:
- void NormalFunc()
- {
- printf("这里是普通函数\n");
- }
- class A
- {
- public:
- static void StaticFunc()
- {
- printf("这里是成员静态函数\n");
- }
- void MemberFunc()
- {
- printf("这里是成员非静态函数\n");
- }
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- //普通函数
- typedef void(*NormalFuncp)();
- //成员函数
- typedef void(A::*MemberFuncp)();
- NormalFuncp fun1 = NormalFunc;
- MemberFuncp fun2 = &A::MemberFunc;
- NormalFuncp fun3 = A::StaticFunc;
- A a;
- fun1();
- (a.*fun2)();
- fun3();
- return 0;
- }
可以看到普通函数指针调用函数的方式和成员非静态函数指针调用函数的方式不同,成员非静态函数指针调用函数需要依赖于该类的一个对象,并且用 .* 或者 ->* 的语法来调用。而成员静态函数调用方式却和普通函数差不多。所以我们需要创建一个委托的基本接口对于不同类型指针的再来派生多态处理。
- class IDelegate
- {
- public:
- virtual ~IDelegate() { }
- virtual bool isType(const std::type_info& _type) = 0;
- virtual void invoke() = 0;
- virtual bool compare(IDelegate *_delegate) const = 0;
- };
这里定义了三个接口,一个是调用,表示调用该Delegate对应的函数指针指向的函数。剩下两个是类型判断,使用了C++的RTTI,动态类型的判断。
接下来我们来派生出能注册普通函数的委托。
- class CStaticDelegate : public IDelegate
- {
- public:
- typedef void (*Func)();
- CStaticDelegate(Func _func) : mFunc(_func) { }
- virtual bool isType(const std::type_info& _type) { return typeid(CStaticDelegate) == _type; }
- virtual void invoke() { mFunc(); }
- virtual bool compare(IDelegate *_delegate) const
- {
- if (0 == _delegate || !_delegate->isType(typeid(CStaticDelegate)) ) return false;
- CStaticDelegate * cast = static_cast<CStaticDelegate*>(_delegate);
- return cast->mFunc == mFunc;
- }
- private:
- Func mFunc;
- };
然后是可以注册指向成员非静态函数的指针的委托,因为指向成员非静态函数的类别是这样的 void (ClassName::*FuncName)();而ClassName又是不确定的所以我们这里要使用模板类来封装:
- template<class T>
- class CMethodDelegate : public IDelegate
- {
- public:
- typedef void (T::*Method)();
- CMethodDelegate(T * _object, Method _method) : mObject(_object), mMethod(_method) { }
- virtual bool isType( const std::type_info& _type) { return typeid(CMethodDelegate<T>) == _type; }
- virtual void invoke()
- {
- (mObject->*mMethod)();
- }
- virtual bool compare(IDelegate *_delegate) const
- {
- if (0 == _delegate || !_delegate->isType(typeid(CMethodDelegate<T>))) return false;
- CMethodDelegate<T>* cast = static_cast<CMethodDelegate<T>*>(_delegate);
- return cast->mObject == mObject && cast->mMethod == mMethod;
- }
- private:
- T * mObject;
- Method mMethod;
- };
这里的类型T是指这个委托注册的成员函数指针所属的类的类别。比如我注册 A::&MemberFunc ,那么这里的T就被替换为A.
其实大家仔细看代码可以发现这两个类十分相似只是invoke() 里面调用的方式不同。还有这里的compare判断是指看两个委托指向的成员函数和对象是否一样,如果只是成员函数一样,绑定的对象不一样也视作不同的委托。
这样我们就把C++中的无返回值、无参数的普通函数指针、成员函数指针封装好了。
最后提供统一的接口去生成”函数指针对象“
- inline IDelegate* newDelegate( void (*_func)() )
- {
- return new CStaticDelegate(_func);
- }
- template<class T>
- inline IDelegate* newDelegate( T * _object, void (T::*_method)() )
- {
- return new CMethodDelegate<T>(_object, _method);
- }
最后我们我们实现委托,这里我们对多个函数指针的存储使用了STL的list.所以头文件中需要引入<list>
- class CMultiDelegate
- {
- public:
- typedef std::list<IDelegate*> ListDelegate;
- typedef ListDelegate::iterator ListDelegateIterator;
- typedef ListDelegate::const_iterator ConstListDelegateIterator;
- CMultiDelegate () { }
- ~CMultiDelegate () { clear(); }
- bool empty() const
- {
- for (ConstListDelegateIterator iter = mListDelegates.begin(); iter!=mListDelegates.end(); ++iter)
- {
- if (*iter) return false;
- }
- return true;
- }
- void clear()
- {
- for (ListDelegateIterator iter=mListDelegates.begin(); iter!=mListDelegates.end(); ++iter)
- {
- if (*iter)
- {
- delete (*iter);
- (*iter) = 0;
- }
- }
- }
- CMultiDelegate& operator+=(IDelegate* _delegate)
- {
- for (ListDelegateIterator iter=mListDelegates.begin(); iter!=mListDelegates.end(); ++iter)
- {
- if ((*iter) && (*iter)->compare(_delegate))
- {
- delete _delegate;
- return *this;
- }
- }
- mListDelegates.push_back(_delegate);
- return *this;
- }
- CMultiDelegate& operator-=(IDelegate* _delegate)
- {
- for (ListDelegateIterator iter=mListDelegates.begin(); iter!=mListDelegates.end(); ++iter)
- {
- if ((*iter) && (*iter)->compare(_delegate))
- {
- if ((*iter) != _delegate) delete (*iter);
- (*iter) = 0;
- break;
- }
- }
- delete _delegate;
- return *this;
- }
- void operator()( )
- {
- ListDelegateIterator iter = mListDelegates.begin();
- while (iter != mListDelegates.end())
- {
- if (0 == (*iter))
- {
- iter = mListDelegates.erase(iter);
- }
- else
- {
- (*iter)->invoke();
- ++iter;
- }
- }
- }
- private:
- CMultiDelegate (const CMultiDelegate& _event);
- CMultiDelegate& operator=(const CMultiDelegate& _event);
- private:
- ListDelegate mListDelegates;
- };
其实最后这个类很像是一个指针容器,然后各个成员方法也只是对这个容器里面的对象进行管理。而主要的三个方法:
重载了 += 表示向这个委托注册一个函数指针,这个方法会自动判重,如果重复了就不会向里面添加。
重载了 -= 表示向这个委托注销一个函数指针,如果这个函数指针不存在就什么也不执行。
重载了 () 表示当作函数调用启动这个委托,内部就是将所有函数指针指向的函数都运行一遍。
到这里,基本上无返回值、无参数的委托就封装好了。我们先来测试一下:
- void Say()
- {
- printf("你好\n");
- }
- class A
- {
- public :
- void Say(){ printf("你不好\n"); }
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- CMultiDelegate onclick;
- onclick += newDelegate(Say);
- onclick += newDelegate(&A(),&A::Say); //注意这里不能传入 new A(), 因为会内存泄漏。
- onclick();
如果以上代码能够成功运行,那么说明你的第一个版本的委托已经封装完毕,但是如何实现任意返回值、任意参数类型、任意参数个数的函数指针的委托呢?
我在网上查阅过许多代码,发现大多数都是使用的宏替换加上多次引用头文件使得每次编译不同参数个数版本的委托,但是这个方法我感觉巧妙但却鸡肋。后来我尝试着使用C11的新特性:可变模板参数实现了这个需求。能够对用户定义的不同委托去自动生成对应的函数指针类型的委托类。
具体的代码详见 C++实现委托机制(二)
C++实现委托机制(一)的更多相关文章
- C++模拟C#事件委托机制(一)
原文来自于http://www.cnblogs.com/netssfy/articles/1652671.html 写了一段时间的C#代码后确实发现C#的事件委托非常好用.于是便想是否在C++中也能如 ...
- Javascript事件模型系列(二)事件的捕获-冒泡机制及事件委托机制
一.事件的捕获与冒泡 由W3C规定的DOM2标准中,一次事件的完整过程包括三步:捕获→执行目标元素的监听函数→冒泡,在捕获和冒泡阶段,会依次检查途径的每个节点,如果该节点注册了相应的监听函数,则执行监 ...
- Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论
Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论 创建用户自定义的类加载器 要创建用户自定义的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的f ...
- Java虚拟机JVM学习05 类加载器的父委托机制
Java虚拟机JVM学习05 类加载器的父委托机制 类加载器 类加载器用来把类加载到Java虚拟机中. 类加载器的类型 有两种类型的类加载器: 1.JVM自带的加载器: 根类加载器(Bootstrap ...
- Invoke() 方法是 Unity3D 的一种委托机制
Invoke() 方法是 Unity3D 的一种委托机制 如: Invoke("SendMsg", 5); 它的意思是:5 秒之后调用 SendMsg() 方法: 使用 Inv ...
- 【Unity3D技巧】在Unity中使用事件/委托机制(event/delegate)进行GameObject之间的通信 (二) : 引入中间层NotificationCenter
作者:王选易,出处:http://www.cnblogs.com/neverdie/ 欢迎转载,也请保留这段声明.如果你喜欢这篇文章,请点[推荐].谢谢! 一对多的观察者模式机制有什么缺点? 想要查看 ...
- java类加载器学习2——自定义类加载器和父类委托机制带来的问题
一.自定义类加载器的一般步骤 Java的类加载器自从JDK1.2开始便引入了一条机制叫做父类委托机制.一个类需要被加载的时候,JVM先会调用他的父类加载器进行加载,父类调用父类的父类,一直到顶级类加载 ...
- Java基础---Java---基础加强---类加载器、委托机制、AOP、 动态代理技术、让动态生成的类成为目标类的代理、实现Spring可配置的AOP框架
类加载器 Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader 类加载器也是Jav ...
- JavaScript 之默认行为 DOM2级,事件委托机制
1. 事件默认行为及阻止方式 1.1 浏览器的默认行为 JavaScript事件本身所具有的属性,例如a标签的跳转,Submit按钮的提交,右键菜单,文本框的输入等. 1.2 ...
- JAVA基础|从Class.forName初始化数据库到SPI破坏双亲委托机制
代码托管在:https://github.com/fabe2ry/classloaderDemo 初始化数据库 如果你写过操作数据库的程序的话,可能会注意,有的代码会在程序的开头,有Class.for ...
随机推荐
- JavaScript之setInterval() 函数
定义和用法 setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式. setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被 ...
- Java反序列化漏洞之殇
ref:https://xz.aliyun.com/t/2043 小结: 3.2.2版本之前的Apache-CommonsCollections存在该漏洞(不只该包)1.漏洞触发场景 在java编写的 ...
- 使用afl-dyninst fuzz无源码的二进制程序
转:http://ele7enxxh.com/Use-AFL-dyninst-To-Fuzz-Blackbox-Binaries.html 使用afl-dyninst fuzz无源码的二进制程序 通常 ...
- maven spring整合mybatis是使用junit测试报字节序列的错误
转载 http://blog.csdn.net/hjun01/article/details/40858753 错误信息 com.sun.org.apache.xerces.internal.impl ...
- 【BZOJ 4403】 4403: 序列统计 (卢卡斯定理)
4403: 序列统计 Time Limit: 3 Sec Memory Limit: 128 MBSubmit: 653 Solved: 320 Description 给定三个正整数N.L和R, ...
- CodeForces - 1009D Relatively Prime Graph
题面在这里! 直接暴力找点对就行了,可以证明gcd=1是比较密集的,所以复杂度略大于 O(N log N) #include<bits/stdc++.h> #define ll long ...
- poj 1984 并查集
题目意思是一个图中,只有上下左右四个方向的边.给出这样的一些边, 求任意指定的2个节点之间的距离. 就是看不懂,怎么破 /* POJ 1984 并查集 */ #include <stdio.h& ...
- 第1篇--基于jdk7和jdk8分析 JVM的内存区域
基于jdk7和jdk8分析 JVM的内存区域 目录前言1.什么是JVM2.JRE/JDK/JVM是什么关系3.JVM执行程序的过程4. JVM的生命周期5.JVM垃圾回收一.运行时数据区的组成1.程 ...
- Java乐观锁实现之CAS操作
介绍CAS操作前,我们先简单看一下乐观锁 与 悲观锁这两个常见的锁概念. 悲观锁: 从Java多线程角度,存在着“可见性.原子性.有序性”三个问题,悲观锁就是假设在实际情况中存在着多线程对同一共享的竞 ...
- 【洛谷】P1196 [NOI2002]银河英雄传说【带权并查集】
P1196 [NOI2002]银河英雄传说 题目描述 公元五八○一年,地球居民迁至金牛座α第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展. 宇宙历七九九年,银河系的 ...