zoukankan      html  css  js  c++  java
  • 实现C++中的事件委托机制

    本文系原创整理,欢迎转载,请标明链接 http://www.cnblogs.com/luming1979

    有问题欢迎加qq讨论:luming_dev@qq.com

    摘要: 介绍了事件委托机制的需求,各种解决方案的演变,最终提出模板化的事件委托机制,并给出较详细的进化过程和原理说明。

    关键词: C++,委托,委托器,事件器,模板



    第一章 基础版实现

    在平时的工作中,我们经常会遇到以下情况

    void Do(int event_id)

    {

        

    }

     

    void OnEvent(int event_id)

    {

         Do(event_id);

    }

    下面是成员函数版本

    class A

    {

    public:

         void Do(int event_id)

         {

            

         }

    };

     

    class B

    {

    public:

         void OnEvent(int event_id)

         {

             a.Do(event_id);

         }

     

    private:

         A    a;

    };

     (这里a或者是B的成员,或者是全局变量,或者通过OnEvent函数传递进来)

    以上是一般情况,当B的OnEvent还需要调用另外的函数或者其他对象的函数时,就不得不对OnEvent函数作出改动,当然如果A的类型改变了,也要做相应改动,变成

    void OnEvent(int event_id)

    {

         c.Run(event_id);

    }

    或者

    void OnEvent(int event_id)

    {

         a.Do(event_id);

         c.Run(event_id);

        

    }

    由于需求的多变性,导致OnEvent函数面对不同的情况有不同的实现,类B的复用性大大降低。我们知道GUI是接收事件并作出处理的一个典型例子,如果按照以上方法,则每一种控件都需要被继承,重载OnEvent函数,用以对应不同的事件响应,是一件很可怕的任务,

    :


    第二章 多态版实现

    2.1 单任务的实现

    C++提供了多态机制,我们可以使用类的虚函数改善以上的问题。

    (在C中可以使用函数指针的方法,其本质是相同的,这个就由读者自己发挥了)

    class EventCallerBase

    {

    public:

         // 基类使用纯虚函数,派生类必须实现

         virtual void Do(int event_id) = 0;

    };

     

    class Receiver

    {

    public:

         void SetEventCaller(EventCallerBase* pCaller) { m_pCaller = pCaller; }

     

         void OnEvent(int event_id)

         {

             if (m_pCaller)

                  m_pCaller->Do(event_id);

         }

     

    private:

         EventCallerBase* m_pCaller;

    };

     

    class EventCallerA : public EventCallerBase

    {

    public:

         virtual void Do(int event_id)

         {

             printf("EventCallerA do event %d.\r\n", event_id);

         }

    };

     

    void main()

    {

         EventCallerA caller;

     

         Receiver receiver;

         receiver.SetEventCaller(&caller);

        

         receiver.OnEvent(99);

    }

    输出:EventCallerA do event 99.

    2.2 多任务的实现

    对于需要对多个对象调用其函数的情况,用以下方式

    EventCallerBase,EventCallerA的实现同上

    class EventCallerB : public EventCallerBase

    {

    public:

         virtual void Do(int event_id)

         {

             printf("EventCallerB do event %d.\r\n", event_id);

         }

    };

     

    class Receiver

    {

    public:

         void AddEventCaller(EventCallerBase* pCaller)

         {

             if (pCaller)

                  m_CallerList.push_back(pCaller);

         }

     

         void OnEvent(int event_id)

         {

             list<EventCallerBase*>::iterator it = m_CallerList.begin();

             while (it != m_CallerList.end())

             {

                  EventCallerBase* pCaller = *it;

                  if (pCaller)

                       pCaller->Do(event_id);

                  ++it;

             }

         }

     

    private:

         list<EventCallerBase*> m_CallerList;

    };

     

    void main()

    {

         EventCallerA callerA;

         EventCallerB callerB;

     

         Receiver receiver;

         receiver.AddEventCaller(&callerA);

         receiver.AddEventCaller(&callerB);

        

         receiver.OnEvent(99);

    }

    输出:EventCallerA do event 99.

          EventCallerB do event 99.

    在以上方法中,类Receiver基本做到了重用,除了OnEvent参数类型和个数的改变,一般情况下,当有事件发生,调用不同的事件处理函数时,只要继承EventCallerBase类,实现Do函数,并在初始阶段设定AddEventCaller即可。这种方法在GUI中,已经能尽可能地重用发生事件部分的类和代码,把主要工作放在实现事件响应的处理上。

    2.3 对已有类的改造

    这里有个问题,如果有一个需求,比如窗口最大化,需要调用成员函数System::Maximize(),怎么办?类System是一个既有类,不能随便改动,来继承EventCallerBase。上面的方法岂不是不实用?

    小小地动动脑筋,方法是有的:

    class System

    {

    public:

         void Maximize(void)   { printf("Window is maximized.\r\n"); }

    };

     

    class EventCallerSystem : public EventCallerBase

    {

    public:

         EventCallerSystem(System* pSystem) { m_pSystem = pSystem; }

     

         virtual void Do(int event_id)

         {

             if (m_pSystem)

                  m_pSystem->Maximize()

         }

     

    private:

         System* m_pSystem;

    };

     

    void main()

    {

         System system;

         EventCallerSystem callerSystem(&system);

     

         Receiver receiver;

         receiver.AddEventCaller(&callerSystem);

        

         receiver.OnEvent(99);

    }

    输出:Window is maximized.

    解决了问题,还留了一个小尾巴,就是要多实现一个EventCallerSystem类。

    有没有办法把这个小尾巴也一并解决掉呢,这就到了这篇文章的主题――C++中的事件委托机制,这次我们用到了C++的另一个特性---模板。


    第三章 事件委托版实现

    3.1 函数指针的使用

    我们首先复习一下函数指针和成员函数指针。

    3.1.1函数指针

    在C和C++语言中,一个命名为my_func_ptr的函数指针指向以一个int和一个char*为参数的函数,这个函数返回一个浮点值,声明如下:

    float (*my_func_ptr)(int, char *);

    为了便于理解,一般我们使用typedef关键字。

    typedef float (*MyFuncPtrType)(int, char *);

    如果你的函数指针指向一个型如float some_func(int, char *)的函数,这样做就可以了:

    MyFuncPtrType my_func_ptr = some_func;

    当你想调用它所指向的函数时,可以这样写:

    (*my_func_ptr)(7, "HelloWorld");

    或者

    my_func_ptr(7, "HelloWorld");

    3.1.2成员函数指针

    在C++程序中,很多函数是成员函数,即这些函数是某个类中的一部分。你不可以像一个普通的函数指针那样指向一个成员函数,正确的做法应该是,你必须使用一个成员函数指针。一个成员函数的指针指向类中的一个成员函数,并有相同的参数,声明如下:

    float (SomeClass::*my_memfunc_ptr)(int, char *);

    将函数指针指向型如float SomeClass::some_member_func(int, char *)的函数,可以这样写:

        my_memfunc_ptr = &SomeClass::some_member_func;

    当你想调用它所指向的成员函数时,可以这样写:

    SomeClass* x = new SomeClass;

    (x->*my_memfunc_ptr)(6, "HelloWorld");


    3.2 函数指针的大小

    class A

    {

    public:

         int Afunc() { return 2; };

    };

     

    class B

    {

    public:

         int Bfunc() { return 3; };

    };

     

    class D: public A, public B

    {

    public:

         int Dfunc() { return 5; };

    };

     

    int main()

    {

         printf("%d\n", sizeof(&main));

         printf("%d\n", sizeof(&A::Afunc));

         printf("%d\n", sizeof(&B::Bfunc));

         printf("%d\n", sizeof(&D::Dfunc));

         return 0;

    }

    输出:

    4

    4

    4

    8

    可以看出,普通函数的指针大小是4,

    普通类的成员函数的指针大小也是4,

    对于多重继承的类,成员函数的指针大小是8,

    还有成员函数指针大小是12和16的情况,在这里就不展开了。

    (需要特别注意的是,相同的代码,在不同的编译器下,函数指针的大小也不相同)。

    对函数指针和成员函数指针的复习就到这里。


    3.3 C++中的事件委托

    以下登场的是本文的主角:模板化实现的C++中的事件委托

    3.3.1代码

    /////////////////////////////////////////////////////////////////////////////////

    /// \class FuncCache

    /// \brief 函数对象寄存器

    /////////////////////////////////////////////////////////////////////////////////

    template <typename ReturnType>

    class FuncCache

    {

         static const int SIZE = 48;

         typedef ReturnType (*func_caller)(FuncCache*);

     

         /// \class MemberFuncAssist

         /// \brief 对象成员函数寄存器的辅助器

         class FuncCacheAssist

         {

         public:

             /// \brief 构造函数,初始化。

             FuncCacheAssist(FuncCache* pFunc)

             {

                  m_Size = 0;

                  m_pFunc = pFunc;

                  // 读取用偏移必须归位

                  m_pFunc->m_Cur = 0;

             }

             /// \brief 析构函数。

             ~FuncCacheAssist(void)

             {

                  // 弹出以前压入的参数

                  if (m_Size > 0)

                       m_pFunc->Pop(m_Size);

             }

             /// \brief 压入指定大小的数据。

             uint Push(const void* pData, uint size)

             {

                  m_Size += size;

                  return m_pFunc->Push(pData, size);

             }

     

             /// 压入参数的大小

             int                m_Size;

             /// 对象成员函数寄存器

             FuncCache*         m_pFunc;

         };

     

    public:

         /// \brief 构造函数,初始化。

         FuncCache(func_caller func)

         {

             m_Size = 0;

             m_Cur = 0;

             m_Func = func;

         }

         /// \brief 压入指定大小的数据。

         uint     Push(const void* pData, uint size)

         {

             size = (size <= SIZE - m_Size)? size : (SIZE - m_Size);

             memcpy(m_Buffer + m_Size, pData, size);

             m_Size += size;

             return size;

         }

         /// \brief 弹出指定大小的数据。

         uint      Pop(uint size)

         {

             size = (size < m_Size)? size : m_Size;

             m_Size -= size;

             return size;

         }

         /// \brief 读取指定大小的数据,返回指针。

         void*         Read(uint size)

         {

             m_Cur += size;

             return (m_Buffer + m_Cur - size);

         }

         /// \brief 执行一个参数的函数。

         ReturnType    Execute(const void* pData)

         {

             // 用辅助结构控制

             FuncCacheAssist assist(this);

             // 压入参数

             assist.Push(&pData, sizeof(void*));

             // 执行函数

             return m_Func(this);

         }

     

    protected:

         /// 对象,函数,参数指针的缓冲区

         uchar         m_Buffer[SIZE];

         /// 缓冲区大小

         uint          m_Size;

         /// 缓冲区读取用的偏移

         uint          m_Cur;

         /// 操作函数的指针

         func_caller   m_Func;

    };

     

     

    /////////////////////////////////////////////////////////////////////////////////

    /// \class MFuncCall_1

    /// \brief 一个参数的成员函数执行体

    /////////////////////////////////////////////////////////////////////////////////

    template <typename ReturnType, typename Caller, typename Func, typename ParamType>

    class MFuncCall_1

    {

    public:

         /// \brief 执行一个参数的成员函数。

         static ReturnType MFuncCall(FuncCache<ReturnType>* pMFunc)

         {

             // 获得对象指针

             Caller* pCaller = *(Caller**)pMFunc->Read(sizeof(Caller*));

             // 获得成员函数指针

             Func func = *(Func*)pMFunc->Read(sizeof(Func));

             // 获得参数的指针

             ParamType* pData = *(ParamType**)pMFunc->Read(sizeof(ParamType*));

             // 执行成员函数

             return (pCaller->*func)(*pData);

         }

    };

    /////////////////////////////////////////////////////////////////////////////////

    /// \class L_SignalRoot

    /// \brief 类型检查严格的事件委托器基类

    /////////////////////////////////////////////////////////////////////////////////

    template <typename ReturnType>

    class L_SignalRoot

    {

    public:

         /// \brief 指定事件名,卸载指定对象的事件委托器。

         template <typename Caller>

         void     MFuncUnregister(Caller* pCaller)

         {

             func_map& func_list = m_MemberFuncMap;

             func_map::iterator it = func_list.find(pCaller);

             if (it != func_list.end())

                  func_list.erase(it);

         }

         /// \brief 清空所有事件委托器。

         void     MFuncClear(void)

         {

             m_MemberFuncMap.clear();

         }

     

    protected:

         typedef map< void*, FuncCache<ReturnType> > func_map;

         /// 事件名和绑定的事件委托器的列表

         func_map m_MemberFuncMap;

    };

     

     

    /////////////////////////////////////////////////////////////////////////////////

    /// \class L_Signal_1

    /// \brief 类型检查严格,一个参数的事件委托器

    /////////////////////////////////////////////////////////////////////////////////

    template <typename ReturnType, typename ParamType>

    class L_Signal_1 : public L_SignalRoot<ReturnType>

    {

    public:

         /// \brief 指定事件名,注册对应的一个参数的事件委托器。

         template <typename Caller, typename Func>

         void     MFuncRegister(Caller* pCaller, Func func)

         {

             // 指定专门处理一个参数的函数执行体

             FuncCache<ReturnType> mfunc(MFuncCall_1<ReturnType, Caller, Func, ParamType>::MFuncCall);

             // 压入对象和函数

             mfunc.Push(&pCaller, sizeof(Caller*));

             mfunc.Push(&func, sizeof(Func));

             // 添加到事件委托器列表

             m_MemberFuncMap.insert(make_pair(pCaller, mfunc));

         }

         /// \brief 指定事件名,调用其对应的一个参数的事件委托器。

         ReturnType    MFuncCall(const ParamType& data)

         {

             // 清空返回值

             ReturnType result;

             memset(&result, 0, sizeof(result));

             // 对于所有委托器,调用注册的函数

             func_map::iterator it = m_MemberFuncMap.begin();

             while (it != m_MemberFuncMap.end())

             {

                  result = it->second.Execute(&data);

                  ++it;

             }

             return result;

         }

    };

     

     

    class EventCallerA

    {

    public:

         bool Do(int event_id)

         {

             printf("EventCallerA do event %d.\r\n", event_id);

             return true;

         }

    };

    class EventCallerB

    {

    public:

         bool Run(int event_id)

         {

             printf("EventCallerB run event %d.\r\n", event_id);

             return true;

         }

    };

     

    void main()

    {

         // 申明返回值是bool类型,参数是int类型,单参数的事件器

         L_Signal_1<bool, int> signal;

         EventCallerA callerA;

         EventCallerB callerB;

    // 注册委托器并调用事件

         signal.MFuncRegister(&callerA, &EventCallerA::Do);

         signal.MFuncRegister(&callerB, &EventCallerB::Run);

         signal.MFuncCall(1);

    }

    注意这里EventCallerA和EventCallerB并没有相同的基类。

    3.3.2名词定义

    先定义一些概念,便于我们统一理解

    事件器:指发生事件后,处理事件的响应,逐个通知事先注册的对象。

    委托器:指某事件发生后,需要被通知,并执行事先注册的函数的对象。

    3.3.3需求

    再谈谈我们的需求:

    1.     某个事件发生后,能通知到所有事先注册过的委托器。

    2.     委托器的类型可能千差万别。

    3.     加入参数使这个机制更灵活,应对每次不同的事件参数,支持1个,2个,甚至更多的参数。

    4.     参数的类型也不希望有限制。

    5.     委托器有执行结果,可以被事件器获取。

    6.     委托器销毁的时候,需要通知事件器,将其从委托器列表中排除。

    3.3.4限制

    最后谈谈可以有的限制

    1.     针对同一个事件,委托器的函数参数类型应该是相同的,顺序也相同,因为事件的参数类型是不变的,否则可以分解为两个事件。由事件起始,通知委托器,如果参数类型各不相同,没有意义。

    2.     函数参数过多也没有意义,因为我们知道,多个参数的需求可以用类或者结构体代替,以减少参数个数。

    3.     大多数情况下,我们只需要知道最后一个委托器的执行结果。

    3.3.5解决方法

    怎么办?抽象!

    如何抽象??往二进制层面抽象!!

    当我们要统一处理一些需求的时候,我们只有把需求看成相同的类型和格式。

    说到底,对象只是内存中的一块数据,而函数也是内存中的一段数据,我们可以用内存地址的方式来统一表示它们。

    3.3.6代码解析

    模板类FuncCache 就是为了实现这一级的抽象而存在,以下对FuncCache做必要解析:

    FuncCache::m_Buffer用来存储对象的指针,成员函数的指针,以及函数参数的指针,暂定大小为固定48字节,其中对象指针4字节,成员函数指针4到16字节不等,参数指针每个为4字节,可以有多个。

    FuncCache::m_Size表示目前用到了多少字节的数据。

    FuncCache::m_Cur表示用来从头依次读取对象指针,成员函数指针和参数指针的数据偏移量。

    FuncCache::m_Func很关键,因为以上都是数据,光有材料还要明确如何处理,其就承担了这个重要的任务,类型是typedef ReturnType (*func_caller)(FuncCache*),对于同种ReturnType,其类型是固定的,这是很关键的一步,完成了从不同类型的对象,不同类型的函数以及参数(有条件的)到一致的对象之间的抽象。

    FuncCache的各个函数很简单,不做详细说明了,值得一提的是其内部类FuncCacheAssist,这个内部类存在的主要价值是在Execute退出的时候,将压入的参数排除。

    接下来解析模板类MFuncCall_1

    这里只是列了对于一个参数的函数委托器的实现,无参数、多参数的实现类似,请读者自行发挥。

    该模板类很简单,只有一个函数,但是提供了很灵活的功能,返回类型,对象类型,成员函数类型,参数类型,全部可以自定义的,也只有通过模板类才能实现所需要的功能,具体函数算法很简单,就不展开了。

    最后是模板类L_SignalRoot和L_Signal_1,也就是事件器。

    ReturnType是模板化的返回类型,ParamType是模板化的单参数类型。

    该类只有一个变量m_MemberFuncMap,用来保存所有的委托器的抽象,也就是FuncCache

    该类提供了四个接口,也是对于使用者最常使用的:

    1.       MFuncRegister  注册委托器对象和函数

    2.       MFuncCall       调用所有注册的委托器,并返回最后调用的结果。

    3.       MFuncUnregister 根据对象指针,删除注册过的委托器,委托器在事件器之前销毁的话,             必须调用这个接口,这点没有做成自动的,主要为了减少类的复杂度。

    4.       MFuncClear      清空所有委托器。

    给用户的接口很简单明了易于使用。

    参考

    《成员函数指针与高性能的C++委托》 Don Clugston

    有问题欢迎加qq讨论:luming_dev@qq.com

  • 相关阅读:
    matlab--“下标索引必须为正整数类型或逻辑类型”
    将中缀表达式转化为后缀表达式
    MATLAB那些常见的命令
    关于实现线程同步的几种方式
    关于http协议
    小白学习之activiti工作流入门
    小白- jquery 学习笔记
    小白-Javascript学习笔记
    小白-css笔记
    小白- html笔记
  • 原文地址:https://www.cnblogs.com/luming1979/p/1963750.html
Copyright © 2011-2022 走看看