自己动手编写c++事件模型

news/2024/7/4 12:59:58 标签: c++, fun, 设计模式, 编译器, class, c#
class="baidu_pl">
class="article_content clearfix">
class="htmledit_views">

在Java、C#等语言或者说其类库中,都实现了事件模型。而c++语言本身并没有定义事件机制,并且在目前众多优秀的c++类库,包括STL、Boost等都没有实现类似的事件机制。当我们被MFC的消息搞得头昏眼花之时,是否有冲动自己去实现一个简单的事件模型呢。我想,有着相同想法的人肯定很多,而真正动手来写可能会碰到各种各样的困难。下面就让我们一步步来编写一个简单的事件模型。
<!--[if !supportLists]-->一、<!--[endif]-->了解事件模型的机制

在开始之前,我们有必要简单的了解一下事件模型的机制。事实上,事件模型的机制不止一种,Java和c#的事件机制就不太一样,不过事件模型的基础都是一样,那就是一般都使用Observerclass="tags" href="/tags/SheJiMoShi.html" title=设计模式>设计模式。关于Observerclass="tags" href="/tags/SheJiMoShi.html" title=设计模式>设计模式,希望详细了解的朋友可以参考《》书,在这里我们就不详细的介绍,只是参照c#的事件机制来实现。

c#中,我们可以以event关键字声明一个事件,然后我们可以将一个函数委托挂接在这个事件上,当事件被调用时,则所有挂接的函数也会被调用。这里有一个比较难以理解的词大概是委托,其实也不难理解。委托其实就是一种函数包装类,这种类可以把任何函数的指针保存起来,等到需要调用的时候再调用,并且这种类的实例必须能够挂接到事件之中。

看了上面这段话,是不是觉得其实事件模型也并不复杂。还想对事件模型了解得更多?我这里就不继续了,感兴趣的朋友可以查找一下相关的资料。下面我们就开始在c++中编写一个简单的事件模型。

<!--[if !supportLists]-->二、<!--[endif]-->设计一个简单的c++事件模型

因为一个事件模型其实就是一个典型的Observerclass="tags" href="/tags/SheJiMoShi.html" title=设计模式>设计模式,因此最重要的就是Subject(目标类)和Observer(观察者类)的设计。

首先,我们需要一个事件类,它其实是一个抽象的Subject。它是一个基类,所有的其他事件都从它继承,并且用户只需要从这个基类继承就可以自定义事件。我们不仿将这个类定义为CEvent。CEvent的声明如下:

class CEvent 

{

public:

         typedef list<CEventHandler> data_type;

         CEvent();

<!--[if !supportEmptyParas]-->          virtual ~CEvent();

<!--[if !supportEmptyParas]--><!--[endif]-->

         void operator()()

         {

                  data_type::iterator it;

                   for (it = m_observer.begin(); it != m_observer.end(); ++it)

                   {

                            (*it)(*this);

                   }

         }

<!--[if !supportEmptyParas]--> <!--[endif]-->

         CEvent& operator+= (const CEventHandler& handler)

         {

                  Register(handler);

                   return *this;

         }

<!--[if !supportEmptyParas]--> <!--[endif]-->

         CEvent& operator-= (const CEventHandler& handler)

         {

                  UnRegister(handler);

                   return *this;

         }

<!--[if !supportEmptyParas]--> <!--[endif]-->

         void Register(const CEventHandler& handler)

         {

                  m_observer.push_back(handler);

         }

<!--[if !supportEmptyParas]--> <!--[endif]-->

         void UnRegister(const CEventHandler& handler)

         {

                  m_observer.remove(handler);

         }

<!--[if !supportEmptyParas]--> <!--[endif]-->

protected:

<!--[if !supportEmptyParas]-->

         data_type m_observer;

};

<!--[if !supportEmptyParas]--> <!--[endif]-->

其次,这个事件可以挂接任意的函数,也就是它可以保存所有的挂接函数。在c++中,我们大致有三种类型的函数,全局或者静态函数、成员函数、仿函数。要在CEvent中实现一个或者多个方法来执行挂接的任务不太现实。我们参考c#的实现方法,可以先实现一个委托类,将各种函数保存起来。然后再使用CEvent对这个委托类进行挂接。我们不仿称这个类为CEventHandler。

然后,我们的这个委托类需要能够保存所有的函数。在c++里,我们能够很容易的得到函数的指针,只需要在函数名前加&,但是各种函数,特别是成员函数它是没有类型的,这就要有一种机制,将所有的函数转换为同一种类型。因为函数是没有类型,也相当于每一个函数都是一种类型,解决这个问题的方法其实就是使用模板类。因此,我们先定义一个虚基类CFunImpl。对三种不同的函数,我们分别从Cclass="tags" href="/tags/FUN.html" title=fun>funImpl继承三种不同的子类,CstaticFun、CmemFun,CFunctor。CstaticFun表示全局的或者静态的函数,不过这必须是一个模板类,对于每一个静态或者全局函数,都是模板类的一个特化。类声明的代码如下:

template <typename Fun>

         class CStaticFun : public CFunImpl

         {

         public:

<!--[if !supportEmptyParas]--> <!--[endif]-->

                  CStaticFun(const Fun& class="tags" href="/tags/FUN.html" title=fun>fun) : m_class="tags" href="/tags/FUN.html" title=fun>fun(class="tags" href="/tags/FUN.html" title=fun>fun){};

                  

                   virtual ~CStaticFun()

                   {

                   }

         protected:

                   Fun m_class="tags" href="/tags/FUN.html" title=fun>fun;

         };

<!--[if !supportEmptyParas]--> <!--[endif]-->

对于CmemFun类,我们如法炮制,不过我们这时需要保存两个成员变量,一个是成员函数的指针,另一个则是该成员函数所属的对象的指针。类声明的代码如下:

<!--[if !supportEmptyParas]--> <!--[endif]-->

template <typename PointerToObj, typename PointerToMemFun>

         class CMemFun : public CFunImpl

         {

         public:

<!--[if !supportEmptyParas]--> <!--[endif]-->

                  CMemFun(const PointerToObj& pObj, PointerToMemFun pMemFn)

                            : m_pObj(pObj), m_pMemFun(pMemFn)

                   {

                   }

<!--[if !supportEmptyParas]--> <!--[endif]-->

virtual ~CMemFun()

                   {

                   }

                  

         protected:

                  PointerToObj m_pObj;

                  PointerToMemFun m_pMemFun;

};

<!--[if !supportEmptyParas]--> <!--[endif]-->

对于仿函数,我们这里先不讲,因为仿函数使用得很少,并且这还涉及到仿函数的概念,有兴趣的朋友可以在阅读完本文之后自己来实现。

好,到现在为止,我们可以将所有的函数使用Cclass="tags" href="/tags/FUN.html" title=fun>funImpl来表示了。因此,我们可以在委托类CEventHandler中保存Cclass="tags" href="/tags/FUN.html" title=fun>funImpl*来达到我们的目的。CEventHandler的声明如下:

         class CEventHandler 

         {

         public:

                   virtual ~CEventHandler()

                   {

                            Clear();

                   }

<!--[if !supportEmptyParas]--> <!--[endif]-->

                  template <class Fun>

                  CEventHandler(const Fun& class="tags" href="/tags/FUN.html" title=fun>fun)

                            : m_pImpl(new CStaticFun<Fun>(class="tags" href="/tags/FUN.html" title=fun>fun))

                   {}

                  

                  CEventHandler(const CEventHandler& class="tags" href="/tags/FUN.html" title=fun>fun)

                            : m_pImpl(NULL)

                   {

                            *this = class="tags" href="/tags/FUN.html" title=fun>fun;

                   }

<!--[if !supportEmptyParas]--> <!--[endif]-->

                   void Clear()

                   {

                            if (m_pImpl)

                            {

                                     delete m_pImpl;

                                     m_pImpl = NULL;

                            }

                   }

<!--[if !supportEmptyParas]--> <!--[endif]-->

                  CEventHandler& operator= (const CEventHandler& class="tags" href="/tags/FUN.html" title=fun>fun)

                   {

                            Clear();

<!--[if !supportEmptyParas]--> <!--[endif]-->

                            if (class="tags" href="/tags/FUN.html" title=fun>fun.m_pImpl)

                            {

                                     m_pImpl = class="tags" href="/tags/FUN.html" title=fun>fun.m_pImpl->Clone();

                            }

<!--[if !supportEmptyParas]--> <!--[endif]-->

                            return *this;

                   }

                  

                   // 成员函数

                  template <typename PointerToObj, typename PointerToMemFun>

                            CEventHandler(const PointerToObj& pObj, const PointerToMemFun& pMemFun)

                            : m_pImpl(new CMemFun<PointerToObj, PointerToMemFun>(pObj, pMemFun))

                   {}

<!--[if !supportEmptyParas]--> <!--[endif]-->

                   void operator()(CEvent& e)

                   {

if (m_pImpl)

                            {

                                     (*m_pImpl)(e);

}

                   }

<!--[if !supportEmptyParas]--> <!--[endif]-->

                   bool operator== (const CEventHandler& handler)

                   {

                            if (m_pImpl == NULL || handler.m_pImpl == NULL)

                            {

                                     return true;

                            }

<!--[if !supportEmptyParas]--> <!--[endif]-->

                            if (typeid(m_pImpl) == typeid(handler.m_pImpl))

                            {

                                     return (*m_pImpl) == (*(handler.m_pImpl));

                            }

<!--[if !supportEmptyParas]--> <!--[endif]-->

                            return false;

                   }

<!--[if !supportEmptyParas]--> <!--[endif]-->

         protected:

                  CFunImpl* m_pImpl;

<!--[if !supportEmptyParas]--> <!--[endif]-->

         };

<!--[if !supportEmptyParas]--> <!--[endif]-->

不过要实现事件机制,我们还需要Cclass="tags" href="/tags/FUN.html" title=fun>funImpl*的子类必须实现几个方法。

第一个方法就是调用函数的方法,我们这里要求重载void operator()(CEvent& e);来达到目的。

第二个方法就是operator==,为什么需要这个方法呢。因为对于CEvent来说,调用UnRegister()时,我们必须找到我们使用Register()添加到list中的函数,这时我们需要判断两个CEventHandler对象是否相等,这是件麻烦事情。CEventHandler保存的是CFunImpl的指针,好吧,这个我们就要求Cclass="tags" href="/tags/FUN.html" title=fun>funImpl的子类必须实现重载operator==。

第三个方法,从Cclass="tags" href="/tags/FUN.html" title=fun>funImpl继承必须实现Clone()方法。Clone()方法可以让CEventHandler实现拷贝。具体的实现方法我们可以查看源码。

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportLists]-->三、<!--[endif]-->使用事件模型

一个简单的事件模型基本完成了。现在我们可以编写一个例子来演示一下如何来使用这个事件模型。其实使用起来很简单。

我们可以声明一个CEvent或者CEvent子类的实例。让需要监听的函数挂接到这个CEvent中,我们实现了Register()函数和+=操作符,都可以使用。挂接的语句就像这样:

CEvent evt;

CobserverTest obj;

evt += CEventHandler(&obj, &(CObserverTest::OnStartEvent));

当事件的operator()方法调用时,就会自动调用我们的监听函数。具体的例子可以查看提供的源代码。

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportLists]-->四、<!--[endif]-->模型的缺陷和改进

到现在为止,我们终于实现了一个简单的事件模型。它可以有效的工作,并且与平台无关。当然,要求你的class="tags" href="/tags/BianYiQi.html" title=编译器>编译器尽量的支持c++标准。如果你的class="tags" href="/tags/BianYiQi.html" title=编译器>编译器对c++98标准支持不够,也许它不能够工作正常。笔者在VC6SP6和VC2005中测试均通过。

不过这个模型还有可以改进的余地。其一、目前只是支持同步操作,即当你的事件进行调用时是在同一线程里顺序进行。要改进到支持异步调用就必须在每次调用时在一个新的线程中进行调用。因此我们需要一个线程机制来完成这个工作。如果在一个特定的平台中使用,如VC中,我们可以在响应函数中创建一个新的线程。

其二、目前的所有的函数参数必须为(CEvent& e),不能写成(CchildEvent& e)。要对这个限制进行改进,就需要class="tags" href="/tags/BianYiQi.html" title=编译器>编译器支持虚模板成员函数的机制。不过在VC6和VC2005中,均不支持这种特性。因此要么寻求它法,要么就等到class="tags" href="/tags/BianYiQi.html" title=编译器>编译器的支持吧。

其三、线程安全,这个简单的事件模型使用到了STL中的list组件,但是并非所有的list实现都是线程安全的。建议大家采用stlport,因为它的实现是线程安全的,如果没有采用线程安全的实现,那么在调用Register和UnRegister时最好能够实现互斥。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zhuzhubin/archive/2008/09/28/2991434.aspx


http://www.niftyadmin.cn/n/1535961.html

相关文章

BZOJ3999

来自蒟蒻XXJ的做题记录 其实这个题目就是一个比较裸的树剖w然后再加上一个线段树维护 首先看题目我们要解决的是一个求解区间里两个数之间差的最大值【绕 而且我们发现这两个数的关系还必须在路径上是有向的&#xff0c;也就是说必须是一个后走到的点减去一个先走到的点【雾 【…

GDP含金量倒数第三与山东人的幸福

2009年GDP前三甲是广东、江苏、山东&#xff0c;分别为3.9万亿&#xff0c;3.4万亿&#xff0c;3.38万亿&#xff1b;2009年GDP含金量排名后三位是山东、新疆、内蒙古。前茅与垫底都有山东&#xff0c;或就是说&#xff0c;山东的GDP“含金量”与实际GDP的落差之大可以用惨不忍…

Mysql 唯一索引长度_关于mysql索引长度的相关内容总结

MySQL优化之-索引具体代码分析&#xff1a;索引是在存储引擎中实现的&#xff0c;因此每种存储引擎的索引都不一定完全相同&#xff0c;并且每种存储引擎也不一定支持所有索引类型。根据存储引擎定义每个表的最大索引数和最大索引长度。所有存储引擎支持每个表至少16个索引&…

Windows下安装Jekyll

一直以来使用jekyll更新文章时都是在Windows下的Linux虚拟机内构建&#xff0c;测试&#xff0c; 因为听闻Windows下安装比较麻烦&#xff0c;不过现在觉得打开虚拟机更麻烦&#xff0c; 所以本着不作死不罢休的精神开始了Windows下jekyll安装之旅... 安装Ruby和RubyDevKit 下载…

qt中文翻译步骤

第一步 在你的pro里面加入 TRANSLATIONS myexec_zh.ts 第二步 用lupdate 操作pro 将要翻译的提取到ts文件 命令是 lupdate my.pro 第三步 用 linguist 打开刚才的ts文件,linugist是在qt的bin的目录里面, 是一个界面工具 打开linguist 后用菜单栏file ->open 打开 相应的ts文…

java的socket包_Java的Unix Socket开发包 JUDS

授权协议: LGPL开发语言: Java操作系统: Linux软件介绍Java Unix Domain Sockets (JUDS) 提供了 Java 的方法用来访问 Unix domain sockets 套接字。示例代码&#xff1a;package com.google.code.juds.test;import java.io.IOException;import java.io.InputStream;import jav…

结对编程1 (201421123084,201421123062)

码市地址&#xff1a;https://coding.net/u/lzx84/p/Calculation/git 题目描述&#xff1a; 不知道大家是否尝试过这样一种开发模式&#xff1a;你有一个伙伴&#xff0c;你们坐在一起&#xff0c;并肩作战&#xff0c;面对着同一台显示器&#xff0c;使用着同一键盘&#xff0…

程序员能力矩阵 你属于哪一层?

注意:每个层次的知识都是渐增的&#xff0c;位于层次n&#xff0c;也蕴涵了你需了解所有低于层次n的知识。 计算机科学 Computer Science 软件工程 Software Engineering 程序设计 Programming 经验 Experience 【CSDN编者按】 上述图书中&#xff0c;第一级对应的英文为Unl…