原文来自于http://www.cnblogs.com/netssfy/articles/1652671.html

写了一段时间的C#代码后确实发现C#的事件委托非常好用。
于是便想是否在C++中也能如此实现。
其实事件委托机制的根本还是回调。由于C#编译器帮我们做了很多事,所以我们在订阅委托时就显的非常的方便。

其实在C++里,实现回调也是非常方便的,函数指针就是为他存在的。
但是,麻烦就麻烦在类成员函数指针的类型上
一个非成员函数void Fun(void)和一个 void Class::Fun(void)的类型是截然不同的。

在C#中,如果定义了一个委托delegate void FunDelegate(void)和事件event FunDelegate event1
那么不管是Class1的成员函数Fun还是Class2上的成员函数Fun都能很方便的订阅到事件上,不需要考虑类的类别
Class1 class1;
Class2 class2;
event1 += class1.Fun;
event1 += class2.Fun;

但是C++则不同,首先Class1中的成员函数Fun的类型为void Class1::Fun(void)
那么这类型的指针就为void Class1::*Fun(void),Class2也同样.
所以他们没有办法定于到一半的函数指针上,即使回返值和参数类型相同,如果这样做,编译器会报类型转换错误.

分析:
对于一个事件委托机制来说,一个事件可以被多个观察者侦听,且不关心这些观察者的类型是什么,即无论是Class1的Fun还是Class2的Fun
都可以侦听同一个类型的事件

设计:
首先,肯定会想到用模板,但是如果CEvent<ObserverType, ArgsType>这样写,那么肯定满足不了上面的条件,因为如果定义
CEvent<typename Class1, typename void>那么肯定不满足,上面的条件,这个时间只能被Class1的实例订阅,那怎么办?

一般来说,根据设计模式,总是存异求同.把相同的部分抽取出来.现在的问题在于CEvent和观察者的类型之间有直接的耦合,要消除这个耦合的话,
我的方法就是加间接.在中间再加一个类型CListenerAgent<typename ObserverType, typename ArgsType>,这个类会知道具体的观察者
类型是什么,并且也知道处理时间的函数的类型(返回值都为void)
然后是消除CEvent对观察者类型的依赖,CEvent只需要触发事件,他不关心到底是哪些类型会去处理这个事件,他要做的只是FireEvent.
也就是说CEvent只需要让CListenerAgent去调度具体是哪个类型的实例会处理这个事件,CEvent只需要使用CListenerAgent,但是我们现在看
到的是CListenerAgent也类型相关的,所以必须抽取出一个接口供CEvent使用:IListenerAgent<typename ArgsType>,这个接口只有一个
函数Fire(ArgsType args).CEvent只要依赖于这个接口就OK了.所以CEvent的定义为CEvent<typename ArgsType>;

下面为源代码
template<typename ArgsType>
class IListenerAgent
{
  public:
  virtual void Fire(ArgsType) = 0;
};

CListenerAgent<typename ObserverType, typename ArgsType>
{
private:
  ObserverType* _instance;
public:
  typedef void (ObserverType::*Delegate)(ArgsType);

  Delegate Handler;

  CListenerAgent()
  {
    Handler = NULL;
    _instance = NULL;
  }

  CListenerAgent(ObserverType* i, Delegate h)
  {
    _instance = i;
    Handler = h;
  }

  void Fire(ArgsT ptr)
  {
   (_instance->*Handler)(ptr);
  }
};

template<typename ArgsType>
class CEvent
{
public:
  IListenerAgent<ArgsType>* _i;
public:
  CEvent()
  {
    _i = NULL;
  }
  void operator()(ArgsType args)
  {
     if (_i = NULL)
    {
      _i->Fire(args);
    }
  }

  void operator+=(IListenerAgent<ArgsType>* i)
  {
    _i = i;
  }

  void operator+=(IListenerAgent<ArgsType>* i)
  {
    _i = NULL;
  }
};

class Observer1
{
public:
  void Handler(int)
  {
  cout << "Observer1::Handler" << endl;
  }
};
class Observer2
{
public:
  void Handler(int)
  {
    cout << "Observer2::Handler" << endl;
  }
};

这样基本完成了,那么使用起来就像
Observer1 o1;
Observer2 o2;

CListenerAgent<Observer1, void*>* l1 = new CListenerAgent<Observer1, void*>(&o1, &Observer::Handler);
CListenerAgent<Observer2, void*>* l2 = new CListenerAgent<Observer2, void*>(&o2, &Observer::Handler);

CEvent<int> event;
event += l1;
event += l2;
event(10);

到这里,基本的思想就是这么实现.但是这样还是会有很多问题,比如如果事件注册好,但是还没触发时,o1的实例被销毁了,之后事件触发时则会访问
无效的内存地址而引起严重的错误.

在下一篇文章中我会解决这个问题,代码我已经写好了

C++模拟C#事件委托机制(一)的更多相关文章

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

    原文 来自于http://www.cnblogs.com/netssfy/archive/2010/02/02/1662056.html 为了解决非法地址访问的冲突,首先需要知道发生该错误的原因是什么 ...

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

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

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

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

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

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

  5. javascript事件委托机制详解

    以个人前端工作面试经历来看,javascript事件委托是问的最多的一类题目之一,熟悉事件委托能够了解你对于javascript的掌握程度. 面试官可能问一下问题,现在有5个li待办事件,需要实现当点 ...

  6. JS 的事件委托机制

    以前写上图所示的鼠标点击触发事件,一般都是用如下所示的给每一个表示列表的标签绑定一个click事件(演示用的例子的框架是React): 毫无疑问,这样是比较繁琐的,以后维护修改改个函数名什么的还不方便 ...

  7. 事件/委托机制(event/delegate)(Unity3D开发之十七)

    猴子原创,欢迎转载.转载请注明: 转载自Cocos2Der-CSDN,谢谢! 原文地址: http://blog.csdn.net/cocos2der/article/details/46539433 ...

  8. js的事件委托机制

    如今的JavaScript技术界里最火热的一项技术应该是‘事件委托(event delegation)’了.使用事件委托技术能让你避免对特定的每个节点添加事件监听器:相反,事件监听器是被添加到它们的父 ...

  9. JS与Jquery的事件委托机制

    传送:http://www.ituring.com.cn/article/467 概念: 什么是事件委托:通俗的讲,事件就是onclick,onmouseover,onmouseout,等就是事件,委 ...

随机推荐

  1. 相机位姿估计1_1:OpenCV:solvePnP二次封装与性能测试

    关键词:OpenCV::solvePnP 文章类型:方法封装.测试 @Author:VShawn(singlex@foxmail.com) @Date:2016-11-27 @Lab: CvLab20 ...

  2. batch

    %0  当前 batch 文件名  %n  第 n 个命令行参数(1 <= n <= 9)  %*  所有的命令行参数  @echo off echo % echo % if not &q ...

  3. 《简明python教程》笔记一

    读<简明Python教程>笔记: 本书的官方网站是www.byteofpython.info  安装就不说了,网上很多,这里就记录下我在安装时的问题,首先到python官网下载,选好安装路 ...

  4. java内存划分

    运行时数据区域 Java虚拟机在执行Java的过程中会把管理的内存划分为若干个不同的数据区域.这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,而有的区域则依赖线程的启动 ...

  5. 底部漂浮DIV

    .buttonDiv{background-color: #4e4f50; border: 2px solid #83ABD3; border-radius: 4px; -khtml-opacity: ...

  6. django表单

    一.主要内容 1.服务端获取HttpRequest信息        1)url相关信息        2)HttpRequest.META中包含的键值对        3)HttpRequest中用 ...

  7. Centos6.5下的Hadoop安装

    开始进行云计算部分的学习,为了存档,写下现在进行过的步骤 需要用到的主要版本: 虚拟机:Vmware Workstation pro 12.5 Linux系统:CentOS6.4 64bit jdk版 ...

  8. python编程(一)汉诺塔

    题目描述 编写move(n, a, b, c)函数,它接收参数n,表示3个柱子A.B.C中第1个柱子A的盘子数量,然后打印出把所有盘子从A借助B移动到C的方法. 例: move(3, 'A', 'B' ...

  9. call 和 apply使用

      call 和 apply 都是为了改变某个函数运行时的 context 即上下文而存在的,换句话说,就是为了改变函数体内部 this 的指向.因为 JavaScript 的函数存在「定义时上下文」 ...

  10. 我需要在Web上完成一个图片上传的功能

    我需要在Web上完成一个图片上传的功能. 这个页面需要能从手机中选择图片上传. 首先,这个页面是从微信上面触发的,所以修改了微信的的入口地址,增加了身份识别号作为传参. 跳转到页面的时候,页面先检查身 ...