• C++事件(Event)机制的实现一例(zz)


    作者:袁晓辉(farproc@gmail.com

     

    声明:

    1、   本文为作者原创,如需转载请保持本文的完整性并注明出自 www.farproc.com http://blog.csdn.net/uoyevoli.

    2、   本文附件中的源代码你可以免费使用并无需注明出处。
       用C++实现事件机制我以前写过一个小例子,但不是很完善,比如Event只能接受全局函数作为handler,类成员方法不可以,还有一个Event只能添加一个handler……最近我的一个程序刚好要用到Event机制,所以我就抽了些时间,重新实现了一下。这个版本应该说是比较完善的,基本上和C#中的Event一样了。点击这里下载源代码

    要使用Event机制主要用到两个模板类:一个是Delegate,它实现了对C++函数指针的封装,当然,如上所述包括类成员函数的指针;另一个是Event,顾名思义它就是主角“事件”了。用以实现Event机制的所有类都被我“圈”在CppEvent命名空间里了,以免“污染”我们的global namespace需要提醒的是,这个版本的实现中用到的C++RTTI,所以用VC编译是别忘了加上/GR编译参数。

     

    Delegate

    先看一下Delegate类的原型:

    namespace CppEvent

    {

        

     

         /*

         *公开给程序使用的Delegate模板类

         *ReturnType 函数返回值类型

         *ArgsType 函数参数类型

         */

         template <typename ReturnType = void , typename ArgsType = void*>

         class Delegate

         {

         public:

             /*

             *构造函数

             *用于包装一个对象的成员函数

             * object 成员函数所属的对象指针

             * function 成员函数的指针

             */

              template<class ObjectType>

                  Delegate(ObjectType* object,  ReturnType(ObjectType::*function)(ArgsType))

             {

                 

             }

             /*

             *构造函数

             *用于包装一个全局函数/static函数

             * function 函数指针

             */

             Delegate(ReturnType(*function)(ArgsType))

             {

                 

             }

    };

    }

    Delegate有两个模板参数ReturnTypeArgsType,第一个指定被代理封装的函数(全局函数/类静态方法或类非成员方法)的返回值类型,第二个指定这个函数的参数类型。注意,由于C++模板只能有固定个数的参数,所以Delegate必须对所封装的函数的参数个数加以限制,也就是说Delegate参数的个数必须固定。既然个数必须固定,那就一个好了,反正我们可以传递结构作参数:)

    Delegate有两个重载的构造函数。第二个是针对全局函数或类静态(static)函数的,只有一个参数,就是把ReturnType作为返回值且带一个ArgsType类型参数的函数的指针(ReturnType(*function)(ArgsType) 注意C风格的函数指针的定义方法)。第一个构造函数是针对类的非静态方法(成员方法)的,它本身也是“模板函数”,模板参数指定被它封装的成员方法所属的类。两个参数,第一个为对象指针,第二个为成员方法的指针。比如有如下类:

    class C

    {

    public:

         int M (double param)

         {

             ......

             return ......;

         }

        

    };

    且有一个类C的实例对象objc

    C objc;

    那么,对于ojbcM方法可以使用下面的DelegateM进行封装:

    Delegate<int, double> DelegateM (&objc, C::M);

    有了delegate,就可以调用它的Invoke方法来调用它所封装的函数,另外,Delegate重载了()操作符(operator)所以,你可以直接在Delegate对象后面加括号和参数进行调用了。比如int n =DelegateM.Invoke(0.2);或直接使用更简便的形式int n= DelegateM(0.2);

     

    Event

    看一下Event的原型:

    namespace CppEvent

    {

         /*

         *事件 模板类

         */

         template <typename ReturnType = void, typename ArgsType = void*, bool MultiCast = true>

         class Event

         {

         public:

             /*

             *该事件处理函数所对应的代理类型

             */

             typedef Delegate<ReturnType, ArgsType> EventHandler;

         public:

             /*

             *构造函数

             */

             Event()

             {

             }

            

    };

    }

    它有三个模板参数,分别为该事件代理(handler)的返回值类型、参数类型和一个标志该事件是否为多播(multicast)的bool值。前两个参数是说明可以用来“订阅”(下面会说明)该事件的Delegate的函数(全局/static或类成员)原型。所谓的“多播”指的是一个事件可以有多个订阅的代理,也就是说当这个事件被激发时,可能会有多个函数被调用,MultiCast的默认值为true,即允许多播。

    一旦定义了一个事件,就可以调用它的+=操作符来把一个合适类型的Delegate对象订阅到改事件。-=运算符可以用来取消一个已经订阅的Delegate对象。订阅到某个事件的代理会在该事件被激发是被调用,该代理被从这个事件取消订阅后就不会再被该事件调用了。

    比如有如下Event类型:

    typedef CppEvent::Event<void, size_t> BalanceChanged;

    和一个该类型的Event

    BalanceChanged OnBalanceChanged;

    可以用如下的方法把一个全局函数代理订阅到该事件:

    OnBalanceChanged += Delegate<void, size_t> (OnTomsAccountBalanceChanged);

    其中OnTomsAccountBalanceChanged的原型如下:

    void OnTomsAccountBalanceChanged(size_t balance)

    {

        

    }

    这样,在OnBalanceChanged被激发时OnTomsAccountBalanceChanged函数就会被调用。激发一个事件可以有下面两种方法:

    调用EventInvoke()方法

    OnBalanceChanged.Invoke(100);

    或直接利用Event()操作符

    OnBalanceChanged(100);

    一个例子

    下面我们以“银行帐户操作”为例子,来说明Event机制的使用。

    首先是Account类:

    //Account.h

    #ifndef _ACCOUNT_H_

    #define _ACCOUNT_H_

     

    #include "../CppEvent/Event.h"

    namespace CppEventExampleAccount

    {

         class Account

         {

         public:

             //帐户操作

             enum OperationType{DepositOp/*存款*/, WithdrawOp/*取款*/};

     

             class BalanceEventArgs

             {   

             public:

                  BalanceEventArgs(OperationType operation, size_t ammount)

                       :theOperation(operation)

                       ,theAmmount(ammount)

                  {

                  };

                  virtual ~BalanceEventArgs()

                  {

                  };

                  OperationType Operation(void) const

                  {

                       return theOperation;

                  }

                  size_t Amount(void) const

                  {

                       return theAmmount;

                  }

             private:

                  OperationType theOperation;

                  size_t theAmmount;

              };

         public:

             //定义"帐户即将被改变"事件

             //该事件接收一个BalanceEventArgs&参数,指定本次操作的具体信息

             //第三个模板参数false指定该事件为"单播(Singlecast)",即只能有一个handler

             typedef CppEvent::Event<bool, BalanceEventArgs&, false> BalanceChanging;

             //定义"帐户已改变"事件

             typedef CppEvent::Event<void, size_t> BalanceChanged;

         public:

             //定义两个事件对象

             BalanceChanging OnBalanceChanging;

             BalanceChanged OnBalanceChanged;

         public:

             //构造函数

             Account(size_t balance = 0)

                  :Balance(balance)

             {

             }

             virtual ~Account()

             {

             }

         public:

             //查询余额

             size_t GetBalance(void) const

             {

                  return Balance;

             }

             //存款方法

             //参数指定所存金额

             bool Deposit(size_t amount)

             {

                  bool ReturnValue = false;

                  //激发"帐户即将被改变"事件,并接收其返回值

                  bool AllowOp = FireChangingEvent(DepositOp, amount);

                  if(AllowOp)//如果允许改变

                  {

                       //增加余额

                       Balance += amount;

                       //激发"帐户已改变"事件

                       FireChangedEvent();

                       ReturnValue = true;

                  }

                  else

                  {

                       ReturnValue = false;

                  }

                  return ReturnValue;

             }

             //取款方法

             //参数指定取款金额

             bool Withdraw(size_t amount)

             {

                  bool ReturnValue = false;

                  if(Balance >= amount)//如果余额足够本次取款

                  {

                       //激发"帐户即将被改变"事件,并接收其返回值

                       bool AllowOp = FireChangingEvent(WithdrawOp, amount);

                       if(AllowOp)

                       {

                           //减少余额

                           Balance -= amount;

                           //激发"帐户已改变"事件

                           FireChangedEvent();

                           ReturnValue = true;

                       }

                  }

                  else//余额不足

                  {

                       ReturnValue = false;

                  }

                  return ReturnValue;

             }

         protected:

             //激发"帐户即将被改变"事件

             bool FireChangingEvent(OperationType operation, size_t amount)

             {

                  bool ReturnValue = false;

                  if(OnBalanceChanging != NULL)

                  {

                       BalanceEventArgs args(operation, amount);

                       ReturnValue =  OnBalanceChanging(args);

                  }

                  else //如果该事件没有handler则默认允许操作

                  {

                       ReturnValue = true;

                  }

                  return ReturnValue;

             }

             //激发"帐户已改变"事件

             void FireChangedEvent(void)

             {

                  OnBalanceChanged(Balance);

             }

         private:

             //帐户余额

             size_t Balance;

         };

    }

    #endif

    然后定义要订阅到Account两个事件的全局函数和MobilePhone类的成员函数:

    //Changing事件的handler

    bool OnTomsAccountBalanceChanging(Account::BalanceEventArgs& args)

    {

         TCHAR* OpName = args.Operation() == Account::DepositOp ? "存款" : "取款";

         cout << "---系统日志 : Tom的帐户余额即将被改动, "

             << OpName << args.Amount() << "";

     

         cout << " 此操作被允许---" << endl;

         return true;

    }

    //Changed事件的handler

    void OnTomsAccountBalanceChanged(size_t balance)

    {

         cout << "---系统日志 : Tom的帐户余额被改动了,当前余额为: " << balance << "*---" << endl;

    }

    //手机

    class MobilePhone

    {

    public:

         void OnAccountBalanceChanged(size_t amount)

         {

             cout << ">>>> 手机短信 : 尊敬的用户,您的帐户余额发生了变化,当前余额为" << amount << ",请注意!" << endl;

         }

     

         bool OnAccountBalanceChanging(Account::BalanceEventArgs&)

         {

             cout << "该方法不会执行" << endl;

             return false;

         }

         //......

         //更多的成员和方法

         //......

    };

     

    然后定义类的实例对象,并编写main函数:

    //建立Tom的帐号

    Account TomsAccount(99999);

     

    //Tom的手机

    MobilePhone TomsPhone;

     

    int _tmain(int argc, _TCHAR* argv[])

    {

     

         //为系统订阅Tom的帐号的"帐户即将被改动"事件

         TomsAccount.OnBalanceChanging += Account::BalanceChanging::EventHandler(OnTomsAccountBalanceChanging);

         //上一行代码也可写为: ........+= Delegate<bool ,Account::BalanceEventArgs&>(OnTomsAccountBalanceChanging);

     

         //为系统订阅Tom的帐号的"帐号已被改动"事件

         TomsAccount.OnBalanceChanged += Account::BalanceChanged::EventHandler(OnTomsAccountBalanceChanged);

     

         //Tom的手机订阅他的帐号的"帐户已被改动"事件

         TomsAccount.OnBalanceChanged += Account::BalanceChanged::EventHandler(&TomsPhone, MobilePhone::OnAccountBalanceChanged);

         //Tom的手机订阅Tom的帐号的"帐号已被改动"事件

         //由于该事件为Singlecast,所以此订阅会失败

         TomsAccount.OnBalanceChanging += Account::BalanceChanging::EventHandler(&TomsPhone, MobilePhone::OnAccountBalanceChanging);

     

     

         bool Success = false;

         //执行一些操作,激发一些事件.相应的事件handler函数会调用

         cout << "即将执行 存款100元 的操作" << endl;

         Success = TomsAccount.Deposit(100);

         cout << (Success ? "操作成功!" : "操作失败!") << endl << endl;

     

         cout << "即将执行 存款5000元 的操作" << endl;

         Success = TomsAccount.Deposit(5000);

         cout << (Success ? "操作成功!" : "操作失败!") <<endl << endl;

     

         cout << "即将执行 取款10000元 的操作" << endl;

         Success = TomsAccount.Withdraw(10000);

         cout << (Success ? "操作成功!" : "操作失败!") <<endl << endl;

     

         cout << "-Tom的要求,退订了他的手机短信通知-"<< endl;

         //退订了Tom的手机短信通知

         TomsAccount.OnBalanceChanged -= Account::BalanceChanged::EventHandler(&TomsPhone, MobilePhone::OnAccountBalanceChanged);

         //退订后TomsPhone将不再接收本事件

         cout << "即将执行 取款50元 的操作" << endl;

         Success = TomsAccount.Withdraw(50);

         cout << (Success ? "操作成功!" : "操作失败!") <<endl << endl;

     

    }

     

    运行该程序,可以看到输出如下:

     

    即将执行 存款100 的操作

    ---系统日志 : Tom的帐户余额即将被改动, 存款100 此操作被允许---

    ---系统日志 : Tom的帐户余额被改动了,当前余额为: 100099*---

    >>>> 手机短信 : 尊敬的用户,您的帐户余额发生了变化,当前余额为100099,请注意!

    操作成功!

     

    即将执行 存款5000 的操作

    ---系统日志 : Tom的帐户余额即将被改动, 存款5000 此操作被允许---

    ---系统日志 : Tom的帐户余额被改动了,当前余额为: 105099*---

    >>>> 手机短信 : 尊敬的用户,您的帐户余额发生了变化,当前余额为105099,请注意!

    操作成功!

     

    即将执行 取款10000 的操作

    ---系统日志 : Tom的帐户余额即将被改动, 取款10000 此操作被允许---

    ---系统日志 : Tom的帐户余额被改动了,当前余额为: 95099*---

    >>>> 手机短信 : 尊敬的用户,您的帐户余额发生了变化,当前余额为95099,请注意!

    操作成功!

     

    -Tom的要求,退订了他的手机短信通知-

    即将执行 取款50 的操作

    ---系统日志 : Tom的帐户余额即将被改动, 取款50 此操作被允许---

    ---系统日志 : Tom的帐户余额被改动了,当前余额为: 95049*---

    操作成功!

     

    Press any key to continue

    main函数里对Account的实例TomsAccount进行操作时,订阅了指定事件的函数和方法会被调用,而当退订了某个函数或方法后,该函数或方法就不再在事件激发时被调用了。

    以上代码在vc7.1中调试通过。最后再提醒一遍,别忘了VC/GR参数哦。

    原文地址:http://www.farproc.com/Article/ShowArticle.asp?ArticleID=198

  • 相关阅读:
    Asp.Net细节性问题精萃(转)
    开发OFFICE插件总结(转)
    校内网开心网数据同步引发的讨论(转)
    C++指针探讨 (三) 成员函数指针 (转)
    C++指针探讨 (二) 函数指针 (转)
    【原创】编程获取PE文件信息的方法(转)
    为.net开发者提供的一份关于存储过程的评论(转)
    C++指针探讨 (一)数据指针 (转)
    如何批量修改PPT字体、大小、颜色(转)
    搜索引擎里的爱人(转)
  • 原文地址:https://www.cnblogs.com/strinkbug/p/650937.html
Copyright © 2020-2023  润新知