C++模板实现事件处理器中的“通用成员函数指针”的调用

来源:互联网 发布:身份证验证算法 编辑:程序博客网 时间:2024/06/06 02:33
我最近在实现一个事件处理器,应用到了一种“通用成员函数指针的注册”,先声明,这个名词是我给起的,不过我觉得并不充分恰当,但也想不出什么更好的词,看完下面的介绍,也考考您,看这玩意叫个啥比较合适。

先说需求:这个事件处理器需要在捕获一个事件后,调用已注册的处理函数。看起来很普通,呵呵,不过,这个事件处理函数不一定是哪个类的成员函数(但肯定是成员函数),函数的形式是一定的,即拥有相同的返回类型和参数列表。而这些事件和处理函数,注册在一个全局的结构体中。

问题的提出:同一个结构体的各字段类型是固定的,所以仔细想一下,这个保存着事件和处理函数的结构体应该怎么定义呢?事件的类型是一定的,但是处理函数呢?需求中明确指出,这个处理函数很有可能是不同类中的成员函数,而成员函数指针在定义时,是需要明确类的,比如:

void (MyClass::*func_ptr)(void);

定义了一个指向MyClass类中的无参数、返回类型为void的成员函数指针。这样看来,我没有办法定义一个适用于任何类成员函数的指针呀?

那么,我能不能定义一个基类 HandlerBase,让所有包含事件处理函数的类都继承自HandlerBase呢?这样我可以在定义结构体时,将事件处理函数指针定义为
void (HandlerBase::*func_ptr)(void);

刚刚有这个想法时,我还禁不住一阵欣喜,但细想才发现,这也是行不通的,因为这样的话,我们必需将所有子类中用到的事件处理函数在HandlerBase中声明为虚函数,这样一来,代码就显得混乱没有章法了,所以,这个方法还是 pass 了吧。

看上去问题可能真的很复杂,那怎么办泥?仔细研究后,感觉只有用模板来解决问题了,具体怎么来设计呢?

(补充:当我写完下面一段话再回读的时候,发现我的表达可能并不是太好懂,那么请您硬着头皮看到最后,我相信您一定能明白我的意思的)

首先,通过上面的分析,看来直接将事件处理函数注册到结构体中是不行的,因为同一个结构体,不可能包含一种以上类型的成员。那么,就让我们把这些类型的成员做个统一处理:让我们声明一个基类(FuncCaller),然后把不同的处理函数注册到它的子类(FuncItem)中去,再通过基类中提供的方法来间接调用处理函数!

不过,我可不想针对每一个可能出现事件处理函数的类,都声明一个对应的 FuncItem(因为他们包含的成员函数指针类型不同),那好,现在到了模板上场的时候了,上代码:
  1. class FuncCaller
  2. {
  3. //
  4. };
  5. template <typename T>
  6. class FuncItem : public FuncCaller
  7. {
  8. //
  9. };
复制代码
有了这样的定义,我就可以在FuncItem中添加一个指针类型,并定义一个成员变量:
  1. typedef void (T::*HandlerPtr)(void* param);
  2. HandlerPtr handler;
复制代码
param是事件到达时,可能传递给处理函数的数据。现在,我们在基类FuncCaller中,定义一个函数,来间接调用事件处理函数:

virtual void func_call(void* obj_ptr, void* param)=0;

之所以为纯虚函数,是因为基类是没有实现它的意义的。另外,形参 obj_ptr 用来传递调用事件处理函数的对象,因为成员函数指针是需要通过类的实例来调用的。

下面来看看FuncItem类对 func_call 的实现:
  1. void func_call(void* obj_ptr, void* param)
  2. {
  3. if( !obj_ptr )
  4. return ;
  5. (((T*)obj_ptr)->*handler)(param);
  6. }
复制代码
差不多了,来看看现在这两个类长成什么样了:
  1. 1class FuncCaller
  2. 2{
  3. 3public:
  4. 4 virtual void func_call(void* obj_ptr, void* param)=0;
  5. 5
  6. 6};
  7. 7
  8. 8template <typename T>
  9. 9class FuncItem : public FuncCaller
  10. 10{
  11. 11private:
  12. 12 typedef void (T::*HandlerPtr)(void* param);
  13. 13 HandlerPtr handler;
  14. 14
  15. 15public:
  16. 16 void func_call(void* obj_ptr, void* param)
  17. 17 {
  18. 18 if( !obj_ptr )
  19. 19 return ;
  20. 20
  21. 21 (((T*)obj_ptr)->*handler)(param);
  22. 22 }
  23. 23
  24. 24};
复制代码
嗯,还不错,基本的形式已经有了,不想写太长,待下一篇,再来看看具体事件处理结构的设计。


我最近在实现一个事件处理器,应用到了一种“通用成员函数指针的注册”,先声明,这个名词是我给起的,不过我觉得并不充分恰当,但也想不出什么更好的词,看完下面的介绍,也考考您,看这玩意叫个啥比较合适。

先说需求:这个事件处理器需要在捕获一个事件后,调用已注册的处理函数。看起来很普通,呵呵,不过,这个事件处理函数不一定是哪个类的成员函数(但肯定是成员函数),函数的形式是一定的,即拥有相同的返回类型和参数列表。而这些事件和处理函数,注册在一个全局的结构体中。

问题的提出:同一个结构体的各字段类型是固定的,所以仔细想一下,这个保存着事件和处理函数的结构体应该怎么定义呢?事件的类型是一定的,但是处理函数呢?需求中明确指出,这个处理函数很有可能是不同类中的成员函数,而成员函数指针在定义时,是需要明确类的,比如:

void (MyClass::*func_ptr)(void);

定义了一个指向MyClass类中的无参数、返回类型为void的成员函数指针。这样看来,我没有办法定义一个适用于任何类成员函数的指针呀?

那么,我能不能定义一个基类 HandlerBase,让所有包含事件处理函数的类都继承自HandlerBase呢?这样我可以在定义结构体时,将事件处理函数指针定义为
void (HandlerBase::*func_ptr)(void);

刚刚有这个想法时,我还禁不住一阵欣喜,但细想才发现,这也是行不通的,因为这样的话,我们必需将所有子类中用到的事件处理函数在HandlerBase中声明为虚函数,这样一来,代码就显得混乱没有章法了,所以,这个方法还是 pass 了吧。

看上去问题可能真的很复杂,那怎么办泥?仔细研究后,感觉只有用模板来解决问题了,具体怎么来设计呢?

(补充:当我写完下面一段话再回读的时候,发现我的表达可能并不是太好懂,那么请您硬着头皮看到最后,我相信您一定能明白我的意思的)

首先,通过上面的分析,看来直接将事件处理函数注册到结构体中是不行的,因为同一个结构体,不可能包含一种以上类型的成员。那么,就让我们把这些类型的成员做个统一处理:让我们声明一个基类(FuncCaller),然后把不同的处理函数注册到它的子类(FuncItem)中去,再通过基类中提供的方法来间接调用处理函数!

不过,我可不想针对每一个可能出现事件处理函数的类,都声明一个对应的 FuncItem(因为他们包含的成员函数指针类型不同),那好,现在到了模板上场的时候了,上代码: class FuncCaller

{

//

 

};

 

template <typename T>

class FuncItem : public FuncCaller

{

//

 

};

复制代码有了这样的定义,我就可以在FuncItem中添加一个指针类型,并定义一个成员变量: typedef void (T::*HandlerPtr)(void* param);

HandlerPtr handler;

复制代码param是事件到达时,可能传递给处理函数的数据。现在,我们在基类FuncCaller中,定义一个函数,来间接调用事件处理函数:

virtual void func_call(void* obj_ptr, void* param)=0;

之所以为纯虚函数,是因为基类是没有实现它的意义的。另外,形参 obj_ptr 用来传递调用事件处理函数的对象,因为成员函数指针是需要通过类的实例来调用的。

下面来看看FuncItem类对 func_call 的实现: void func_call(void* obj_ptr, void* param)

{

    if( !obj_ptr )

        return ;

 

    (((T*)obj_ptr)->*handler)(param);

}

复制代码差不多了,来看看现在这两个类长成什么样了: 1class FuncCaller

2{

3public:

4    virtual void func_call(void* obj_ptr, void* param)=0;

5

6};

7

8template <typename T>

9class FuncItem : public FuncCaller

10{

11private:

12    typedef void (T::*HandlerPtr)(void* param);

13    HandlerPtr handler;

14

15public:

16    void func_call(void* obj_ptr, void* param)

17    {

18        if( !obj_ptr )

19            return ;

20

21        (((T*)obj_ptr)->*handler)(param);

22    }

23

24};

复制代码嗯,还不错,基本的形式已经有了,不想写太长,待下一篇,再来看看具体事件处理结构的设计。
上一篇的最后,好像缺了点东西,呵呵,看来我还得给他补上。您有没有发现FuncItem有点问题吗?谁来给成员handler赋值呢?交给构造函数吧,改造后的FuncItem如下:

  1. template <typename T>
  2. class FuncItem : public FuncCaller
  3. {
  4. private:
  5. typedef void (T::*HandlerPtr)(void* param);
  6. const HandlerPtr handler;
  7. public:
  8. void func_call(void* obj_ptr, void* param)
  9. {
  10. if( !obj_ptr )
  11. return ;
  12. (((T*)obj_ptr)->*handler)(param);
  13. }
  14. FuncItem(const HandlerPtr ptr):handler(ptr) {} // 此处为新添加代码
  15. };
复制代码

现在我们已经写好了 FuncCaller 和它的子类了,开始定义我们的结构体吧!这个结构体应该包含的信息,应该有事件及对应的处理函数,不过,处理函数我已经可以用 FuncCaller 来代替了,所以,具体定义如下:

  1. typedef struct _EventRegister
  2. {
  3. EventType event;
  4. FuncCaller* func_caller;
  5. } EventRegister;
复制代码

至此,大功告成!为了测试我们的成果,让我们做一些工作吧:

1、写两个包含事件处理函数的类:

  1. class ClassA
  2. {
  3. public:
  4. void show_me(void* param)
  5. { printf("I'm ClassA, show_me is called! param is 0x%x/n", param); }
  6. };
  7. class ClassB
  8. {
  9. public:
  10. void come_on(void* param)
  11. { printf("I'm ClassB, come_on is called! param is 0x%x", param); }
  12. };
复制代码

2、类有了,再来声明一个注册事件的结构体:

  1. EventRegister event_center[] = {
  2. { EVENT_TEST_1 , new FuncItem<ClassA>(&ClassA::show_me) },
  3. { EVENT_TEST_2 , new FuncItem<ClassB>(&ClassB::come_on) },
  4. { EVENT_INVALID , NULL }
  5. };
复制代码

最后一项用来标识事件注册的结束。


3、写一个触发事件的函数:

  1. /**
  2. * Function:send_event
  3. * params:
  4. * event -> event type
  5. * target -> who will process the event
  6. */
  7. void send_event(EventType event, void* target, void* param)
  8. {
  9. if( !target )
  10. return ;
  11. int loop_event = 0;
  12. EventType et = event_center[loop_event].event;
  13. while( et!=EVENT_INVALID )
  14. {
  15. if( et==event )
  16. {
  17. event_center[loop_event].func_caller->func_call(target, param);
  18. break;
  19. }
  20. et = event_center[++loop_event].event;
  21. }
  22. }
复制代码

现在,来写个main吧!

  1. void main()
  2. {
  3. ClassA ca;
  4. ClassB cb;
  5. send_event(EVENT_TEST_1, (void*)&ca, (void*)0xff00);
  6. send_event(EVENT_TEST_2, (void*)&cb, NULL);
  7. }
复制代码

好,运行之前我还得说一下,估计大家也都看到了,我在结构体中注册的事件处理器,与main中调用 send_event时传入的类实例很可能不属于同一个类,比如我在send_event调用时,将cb的指针传递给了事件EVENT_TEST_1,而不是ca的指针,这样会导致不可预期的运行结果。不过,幸好我们正在写的只是测试用的代码,而实际中应用的事件处理调度程序,要比这复杂得多,而且,具体传递给哪个实例来处理这个事件,是由调度程序来判断的,所以,一个好的调试程序的设计,也是至关重要的。不过,就算有再好的设计,我们也不能忽略代码中存在的风险。其实,我们眼前的这个风险,是有解决办法的,不过,先要行看看正常的运行结果:

I'm ClassA, show_me is called! param is 0xff00
I'm ClassB, come_on is called! param is 0x0

相当理想。别闲着,试试我们刚说的那个“风险”存在的真实性吧!

稍稍修改一个我们的ClassA:

  1. class ClassA
  2. {
  3. private:
  4. char* name;
  5. public:
  6. void show_me(void* param)
  7. { printf("I'm ClassA, show_me is called! name is %s/n", name); }
  8. ClassA():name("ClassA") {}
  9. };
复制代码

好,先编译运行一下,看看效果:

I'm ClassA, show_me is called! name is ClassA
I'm ClassB, come_on is called! param is 0x0

没问题,现在让我们检验风险的存在,修改 main 中的第一个 send_event:

send_event(EVENT_TEST_1, /*(void*)&ca*/(void*)&cb, (void*)0xff00); // Note: 'ca' is replaced by 'cb'

再编译运行一下:


Segmentation fault


哈哈,正是我们预想的,原因说起来又一百字,我就省略了,但是效果是明显的。看来,再写下一篇的理由已经很充分了呀!

 

http://www.pin5i.com/showtopic-24818.html