C++模板实现事件处理器中的“通用成员函数指针”的调用(一)
来源:互联网 发布:编程好看的字体 编辑:程序博客网 时间:2024/06/10 06:35
我最近在实现一个事件处理器,应用到了一种“通用成员函数指针的注册”,先声明,这个名词是我给起的,不过我觉得并不充分恰当,但也想不出什么更好的词,看完下面的介绍,也考考您,看这玩意叫个啥比较合适。
先说需求:这个事件处理器需要在捕获一个事件后,调用已注册的处理函数。看起来很普通,呵呵,不过,这个事件处理函数不一定是哪个类的成员函数(但肯定是成员函数),函数的形式是一定的,即拥有相同的返回类型和参数列表。而这些事件和处理函数,注册在一个全局的结构体中。
问题的提出:同一个结构体的各字段类型是固定的,所以仔细想一下,这个保存着事件和处理函数的结构体应该怎么定义呢?事件的类型是一定的,但是处理函数呢?需求中明确指出,这个处理函数很有可能是不同类中的成员函数,而成员函数指针在定义时,是需要明确类的,比如:
定义了一个指向MyClass类中的无参数、返回类型为void的成员函数指针。这样看来,我没有办法定义一个适用于任何类成员函数的指针呀?
那么,我能不能定义一个基类 HandlerBase,让所有包含事件处理函数的类都继承自HandlerBase呢?这样我可以在定义结构体时,将事件处理函数指针定义为
void (HandlerBase::*func_ptr)(void);
刚刚有这个想法时,我还禁不住一阵欣喜,但细想才发现,这也是行不通的,因为这样的话,我们必需将所有子类中用到的事件处理函数在HandlerBase中声明为虚函数,这样一来,代码就显得混乱没有章法了,所以,这个方法还是 pass 了吧。
看上去问题可能真的很复杂,那怎么办泥?仔细研究后,感觉只有用模板来解决问题了,具体怎么来设计呢?
(补充:当我写完下面一段话再回读的时候,发现我的表达可能并不是太好懂,那么请您硬着头皮看到最后,我相信您一定能明白我的意思的)
首先,通过上面的分析,看来直接将事件处理函数注册到结构体中是不行的,因为同一个结构体,不可能包含一种以上类型的成员。那么,就让我们把这些类型的成员做个统一处理:让我们声明一个基类(FuncCaller),然后把不同的处理函数注册到它的子类(FuncItem)中去,再通过基类中提供的方法来间接调用处理函数!
不过,我可不想针对每一个可能出现事件处理函数的类,都声明一个对应的 FuncItem(因为他们包含的成员函数指针类型不同),那好,现在到了模板上场的时候了,上代码:
有了这样的定义,我就可以在FuncItem中添加一个指针类型,并定义一个成员变量:
param是事件到达时,可能传递给处理函数的数据。现在,我们在基类FuncCaller中,定义一个函数,来间接调用事件处理函数:
之所以为纯虚函数,是因为基类是没有实现它的意义的。另外,形参 obj_ptr 用来传递调用事件处理函数的对象,因为成员函数指针是需要通过类的实例来调用的。
下面来看看FuncItem类对 func_call 的实现:
差不多了,来看看现在这两个类长成什么样了:
现在我们已经写好了 FuncCaller 和它的子类了,开始定义我们的结构体吧!这个结构体应该包含的信息,应该有事件及对应的处理函数,不过,处理函数我已经可以用 FuncCaller 来代替了,所以,具体定义如下:
至此,大功告成!为了测试我们的成果,让我们做一些工作吧:
1、写两个包含事件处理函数的类:
2、类有了,再来声明一个注册事件的结构体:
最后一项用来标识事件注册的结束。
好,运行之前我还得说一下,估计大家也都看到了,我在结构体中注册的事件处理器,与main中调用 send_event时传入的类实例很可能不属于同一个类,比如我在send_event调用时,将cb的指针传递给了事件EVENT_TEST_1,而不是ca的指针,这样会导致不可预期的运行结果。不过,幸好我们正在写的只是测试用的代码,而实际中应用的事件处理调度程序,要比这复杂得多,而且,具体传递给哪个实例来处理这个事件,是由调度程序来判断的,所以,一个好的调试程序的设计,也是至关重要的。不过,就算有再好的设计,我们也不能忽略代码中存在的风险。其实,我们眼前的这个风险,是有解决办法的,不过,先要行看看正常的运行结果:
相当理想。别闲着,试试我们刚说的那个“风险”存在的真实性吧!
稍稍修改一个我们的ClassA:
好,先编译运行一下,看看效果:
没问题,现在让我们检验风险的存在,修改 main 中的第一个 send_event:
再编译运行一下:
哈哈,正是我们预想的,原因说起来又一百字,我就省略了,但是效果是明显的。
ClassA ca;
ClassB cb;
if( typeid(ca)==typeid(cb) )
不过,在使用typeid时需要包含typeinfo头文件,而且,在gcc里,RTTI是有开关的,不过默认为开启,如果你想关掉它,可以加上 -fno-rtti选项。
typeid的另一个功能,是可以通过name操作来得到类型名称:
printf("ca is a %s\n", typeid(ca).name() );
不过,name操作返回的字符串,并不一定就是你在代码中定义的类型名称,具体和编译器有关。
但是,typeid是非常严格的,即属于父子关系的两个类,通过typeid操作得到的返回值是不同的。比如,若ClassB继承自ClassA,那么:
这个判断的结果是 not equal。
再来简单说一下 dynamic_cast,C++中的这个关键字用来提供更安全的运行时类型转换。还是上面的实例,如果ClassB继承自ClassA,那么:
这个实例,pcb是可以成功转化为pca的,但是,下面再添加一个与ClassA没有继承关系的ClassC,那么:
这时,pcc是不可以转化 为pca的,此时pca的值为NULL。还需要说明的是,如果 dynamic_cast将要转换的是一个指针,那么失败后会返回NULL;如果将要交换的是一个引用,那么失败后会抛出一个异常。
由此看来,dynamic_cast比typeid要智能许多了,但是它是以牺牲更多的系统开销做为代价的。
言规正传,在我们的事件处理器中,我准备使用typeid来做为类型判断的工具。不过,在实现这个想法时,还遇到了一个小波折。
回忆一下我们相关类的定义:
我本想在 21 行处增加对类型的判断,即:
可以在用g++翻译的时候却被告知:
'void*' is not a pointer-to-object type
看来 void* 是一种无类型的指针,是不允许对其指向的对象做 typeid 操作的。
在一翻冥思苦想后,除了定义一个基类,再也没有找到一个更好的解决办法。假设基类名称为 HandleBase,那么,我们可以让所以包含事件处理函数的类都继承自HandleBase,并对func_call做如下修改:
这样做就绝对没有问题了。话说回来,我之所以不想为包含事件处理函数的类定义一个基类,就是不想增加类定义的复杂性,同时也可以提高事件处理器的通用性,但是,现在为了实现RTTI,却不得不牺牲这个原则了。不过还好,幸好只是继承一个基类而已。
既然增加一个基类已成定局,那么,就让我们好好利用这个基类,来做更多的事情。为了弥补前面说到的typeid中name操作可能不会返回原始类名,那么就让我们在基类里来记录一份原始类名吧,看看类 HandleBase 的定义:
奇怪,为什么不把名称定义成一个成员变量,而要定义成一个纯虚函数呢?还有check_HandleBase_derived,这又是做什么的?别急,我们再来定义一个宏:
呵呵,别怨我卖关子,坚持看完下面ClassA的定义,您一定能明白:
完成了!呵呵,怎么样,明白了吗?让我来解释上面的一连串动作吧,我准备采用倒序法来说明。先看ClassA的定义,它继承自HandleBase,原因刚才已经就说过了,至于在类声明的起始处调用宏Registe_Handle_Class,则是对相关操作的一个简化。那么,这个宏都做了哪些操作呢?再来看宏的定义,这时您会发现,这个宏其实就是实现我刚才所说的得到原始类名称的功能。宏的实现中,自动定义了基类HandleBase中的_object_real_type函数,使得外界可以通过_object_real_type调用得到对象的原始类名称。这也就是我为什么不定义一个存储类名的成员变量,而要定义一个纯虚函数的原因:定义成变量,是没办法在派生类的声明中对变量进行赋值的,这样的话,类的实现者就必须手动在类的构造函数中来初始化这个变量。而定义成纯虚函数,就是我心里的小算盘了,你继承了我的类,但是没有调用我的宏,那是编译不过去的!看我是多么的为类的使用者——和我自己——而着想啊!另外还有一个static版的_st_object_real_type,功能是一样的,目的是在没有类对象的情况下,也能够得到类名称。而且,我计划在FuncItem类中的特定地方通过 T::_st_object_real_type 得到原始类名,这样,实例化的FuncItem对象就必须是包含有_st_object_real_type调用的类了,这样的类要么是你自己定义_st_object_real_type(什么人会这么干呢),要么调用Registe_Handle_Class宏,也就是继承自HandleBase,于是,我们事件处理器的安全性又被加强了。
接下来说说check_HandleBase_derived,这看上去似乎是所有定义里最诡异的地方了。其实,我之前已经保证了所有继承自HandleBase的类都必须调用宏Registe_Handle_Class,而check_HandleBase_derived就是为了保证所有调用了宏Registe_Handle_Class的类,都必须继承自HandleBase。因为,如果你没有继承自HandleBase,那么,是找不到check_HandleBase_derived的实现的。同时,check_HandleBase_derived被直接定义在类的声明中,所以,会被看成是inline而直接被函数体所替换,因此,_object_real_type不会因为多调用了一次check_HandleBase_derived而产生更多的栈资源开销。
唉,写了这么多,可是还没有完,因为我们既然已经将类的原始名称记录了下来,那么,就和typeid的name操作说再见吧,修改一下func_call函数:
怎么样?万事大吉了吗?没有!别怪没完没了,因为我实在不想把这个话题再延续到下一篇博文了,就让我们在这里一并都解决了吧!
看看您键盘上的Ctrl、C、V是不是磨损最严重的呢?呵呵,干我们这行,这几个键是基本功,不是我不鼓励创新啊,可是有些地方相似的代码直接拷贝过来,是最效率的办法,当然,我们的许多错误,也是这样产生的,就像现在一样!我已经写好了ClassA,它继承了HandleBase,我还需要一个ClassB,也一样要继承自HandleBase,不用多说,拷贝ClassA的一部分定义过来(拷贝多少就看您自己了),改个类名不就完了嘛。呵呵,在此,我估计2/3的朋友会忘记更改Registe_Handle_Class宏参数中的类名称为ClassB!不是我低估您,如果您真的想到了这一点,那您是属于那1/3部分的。虽然这个疏漏并不影响代码运行,但是,真的运行时出错的话,我们可能会被错误提示搞得一头雾水,比如:
Runtime error:get ClassA object while expecting ClassA
为了您着想,我从心眼里想帮您解决这个问题,可是,有办法吗?当然有!我们不是有宏嘛,随便在宏里添加自己的代码,反正使用的人又不知道!那么,就让我们来检查一下这个类的类型吧,修改宏定义如下:
前半部分没变,变得是添加了private部分内容。好了,先不分析代码,让我们把刚才说的场景带到这里来试一下。如果在ClassB的定义中,调用了Registe_Handle_Class(ClassA),于是,private部分会被展开成这样:
注意,这可是在ClassB的定义中!那么,调用ClassA的私有成员函数就成了一件不可能完成的任务了!
唉,现在真的大功告成了,是不是代码有些乱了?代码有些多了,就不再帖上来,请您点击这里下载最终的代码,并和我一起来检验一下我们的成果吧!
先正常运行一下:
好,将ClassA的定义中去掉 public HandleBase,保留对宏Registe_Handle_Class的调用,编译出错:
再将 public HandleBase 恢复,注释掉对宏Registe_Handle_Class的调用,编译出错:
呵呵,这就验证了Registe_Handle_Class宏和继承HandleBase是必须同时出现的。下面恢复ClassA的正常定义,将ClassB定义中对宏Registe_Handle_Class调用的参数改为 ClassA,编译出错:
OK,看来想骗我也是没这么容易的了!再来注释掉ClassB中继承HandleBase的代码和调用宏Registe_Handle_Class的代码,同时注释掉main函数中向ClassB的对象cb发送事件的代码,编译出错:
哈哈,太帅了,不继承HandleBase就别想在事件处理器中注册!好,恢复所有的正常代码,现在该验证我们的RTTI了!修改main函数中对发送EVENT_TEST_1事件的处理对象为cb:
编译没问题,看运行结果:
哇,我们的目的达到了!看看我们的成果吧,有没有一种满足感呢?
先说需求:这个事件处理器需要在捕获一个事件后,调用已注册的处理函数。看起来很普通,呵呵,不过,这个事件处理函数不一定是哪个类的成员函数(但肯定是成员函数),函数的形式是一定的,即拥有相同的返回类型和参数列表。而这些事件和处理函数,注册在一个全局的结构体中。
问题的提出:同一个结构体的各字段类型是固定的,所以仔细想一下,这个保存着事件和处理函数的结构体应该怎么定义呢?事件的类型是一定的,但是处理函数呢?需求中明确指出,这个处理函数很有可能是不同类中的成员函数,而成员函数指针在定义时,是需要明确类的,比如:
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
{
//
};
{
//
};
template <typename T>
class FuncItem : public FuncCaller
{
//
};
有了这样的定义,我就可以在FuncItem中添加一个指针类型,并定义一个成员变量:
typedef void (T::*HandlerPtr)(void* param);
HandlerPtr handler;
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);
}
{
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};
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如下:
template <typename T>
class FuncItem : public FuncCaller
{
private:
typedef void (T::*HandlerPtr)(void* param);
const HandlerPtr handler;
public:
void func_call(void* obj_ptr, void* param)
{
if( !obj_ptr )
return ;
(((T*)obj_ptr)->*handler)(param);
}
FuncItem(const HandlerPtr ptr):handler(ptr) {} // 此处为新添加代码
};
class FuncItem : public FuncCaller
{
private:
typedef void (T::*HandlerPtr)(void* param);
const HandlerPtr handler;
public:
void func_call(void* obj_ptr, void* param)
{
if( !obj_ptr )
return ;
(((T*)obj_ptr)->*handler)(param);
}
FuncItem(const HandlerPtr ptr):handler(ptr) {} // 此处为新添加代码
};
现在我们已经写好了 FuncCaller 和它的子类了,开始定义我们的结构体吧!这个结构体应该包含的信息,应该有事件及对应的处理函数,不过,处理函数我已经可以用 FuncCaller 来代替了,所以,具体定义如下:
typedef struct _EventRegister
{
EventType event;
FuncCaller* func_caller;
} EventRegister;
{
EventType event;
FuncCaller* func_caller;
} EventRegister;
至此,大功告成!为了测试我们的成果,让我们做一些工作吧:
1、写两个包含事件处理函数的类:
class ClassA
{
public:
void show_me(void* param)
{ printf("I'm ClassA, show_me is called! param is 0x%x\n", param); }
};
class ClassB
{
public:
void come_on(void* param)
{ printf("I'm ClassB, come_on is called! param is 0x%x", param); }
};
{
public:
void show_me(void* param)
{ printf("I'm ClassA, show_me is called! param is 0x%x\n", param); }
};
class ClassB
{
public:
void come_on(void* param)
{ printf("I'm ClassB, come_on is called! param is 0x%x", param); }
};
2、类有了,再来声明一个注册事件的结构体:
EventRegister event_center[] = {
{ EVENT_TEST_1 , new FuncItem<ClassA>(&ClassA::show_me) },
{ EVENT_TEST_2 , new FuncItem<ClassB>(&ClassB::come_on) },
{ EVENT_INVALID , NULL }
};
{ EVENT_TEST_1 , new FuncItem<ClassA>(&ClassA::show_me) },
{ EVENT_TEST_2 , new FuncItem<ClassB>(&ClassB::come_on) },
{ EVENT_INVALID , NULL }
};
最后一项用来标识事件注册的结束。
3、写一个触发事件的函数:
/**
* Function:send_event
* params:
* event -> event type
* target -> who will process the event
*/
void send_event(EventType event, void* target, void* param)
{
if( !target )
return ;
int loop_event = 0;
EventType et = event_center[loop_event].event;
while( et!=EVENT_INVALID )
{
if( et==event )
{
event_center[loop_event].func_caller->func_call(target, param);
break;
}
et = event_center[++loop_event].event;
}
}
* Function:send_event
* params:
* event -> event type
* target -> who will process the event
*/
void send_event(EventType event, void* target, void* param)
{
if( !target )
return ;
int loop_event = 0;
EventType et = event_center[loop_event].event;
while( et!=EVENT_INVALID )
{
if( et==event )
{
event_center[loop_event].func_caller->func_call(target, param);
break;
}
et = event_center[++loop_event].event;
}
}
现在,来写个main吧!
void main()
{
ClassA ca;
ClassB cb;
send_event(EVENT_TEST_1, (void*)&ca, (void*)0xff00);
send_event(EVENT_TEST_2, (void*)&cb, NULL);
}
{
ClassA ca;
ClassB cb;
send_event(EVENT_TEST_1, (void*)&ca, (void*)0xff00);
send_event(EVENT_TEST_2, (void*)&cb, NULL);
}
好,运行之前我还得说一下,估计大家也都看到了,我在结构体中注册的事件处理器,与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
I'm ClassB, come_on is called! param is 0x0
相当理想。别闲着,试试我们刚说的那个“风险”存在的真实性吧!
稍稍修改一个我们的ClassA:
class ClassA
{
private:
char* name;
public:
void show_me(void* param)
{ printf("I'm ClassA, show_me is called! name is %s\n", name); }
ClassA():name("ClassA") {}
};
{
private:
char* name;
public:
void show_me(void* param)
{ printf("I'm ClassA, show_me is called! name is %s\n", name); }
ClassA():name("ClassA") {}
};
好,先编译运行一下,看看效果:
I'm ClassA, show_me is called! name is ClassA
I'm ClassB, come_on is called! param is 0x0
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
哈哈,正是我们预想的,原因说起来又一百字,我就省略了,但是效果是明显的。
首先,我们来分析一下风险形成的原因:
Step 1. 声明类A的成员函数指针,并让它指向类A的一个成员函数
Step 2. 将类B实例的指针强制转化为类A的指针,并通过它来调用类A的成员函数指针。因为类A与类B的结构是不一样的,所以这样做的结果也是不可预期的
那么,最好在Step2中,能够检测到指针的实际类型,并与类A的类型进行对比,不一至的话,就不再调用成员函数指针了。说到这,估计大家一定想到C++中的RTTI了,即运行时类型识别。而RTTI也可以通过两种方式来实现,一种是使用关键字typeid,还有一种就是使用 dynamic_cast。
简单说一下typeid,这个关键字可以返回一个常量的引用,我们可以对两个对象执行typeid操作,并通过判断其返回值是否相等,来检查两个对象是否是同一类型。举个例子:
ClassA ca;
ClassB cb;
if( typeid(ca)==typeid(cb) )
不过,在使用typeid时需要包含typeinfo头文件,而且,在gcc里,RTTI是有开关的,不过默认为开启,如果你想关掉它,可以加上 -fno-rtti选项。
typeid的另一个功能,是可以通过name操作来得到类型名称:
printf("ca is a %s\n", typeid(ca).name() );
不过,name操作返回的字符串,并不一定就是你在代码中定义的类型名称,具体和编译器有关。
但是,typeid是非常严格的,即属于父子关系的两个类,通过typeid操作得到的返回值是不同的。比如,若ClassB继承自ClassA,那么:
ClassA ca;
ClassB cb;
if( typeid(ca)==typeid(cb) )
// equal
else
// not equal
ClassB cb;
if( typeid(ca)==typeid(cb) )
// equal
else
// not equal
这个判断的结果是 not equal。
再来简单说一下 dynamic_cast,C++中的这个关键字用来提供更安全的运行时类型转换。还是上面的实例,如果ClassB继承自ClassA,那么:
extern ClassB *pcb;
ClassA *pca = dynamic_cast<ClassB*>(pcb);
ClassA *pca = dynamic_cast<ClassB*>(pcb);
这个实例,pcb是可以成功转化为pca的,但是,下面再添加一个与ClassA没有继承关系的ClassC,那么:
extern ClassC *pcc;
ClassA *pca = dynamic_cast<ClassC*>(pcc);
ClassA *pca = dynamic_cast<ClassC*>(pcc);
这时,pcc是不可以转化 为pca的,此时pca的值为NULL。还需要说明的是,如果 dynamic_cast将要转换的是一个指针,那么失败后会返回NULL;如果将要交换的是一个引用,那么失败后会抛出一个异常。
由此看来,dynamic_cast比typeid要智能许多了,但是它是以牺牲更多的系统开销做为代价的。
言规正传,在我们的事件处理器中,我准备使用typeid来做为类型判断的工具。不过,在实现这个想法时,还遇到了一个小波折。
回忆一下我们相关类的定义:
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 const 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(const HandlerPtr ptr):handler(ptr) {}
25
26};
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 const 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(const HandlerPtr ptr):handler(ptr) {}
25
26};
我本想在 21 行处增加对类型的判断,即:
void func_call(void* obj_ptr, void* param)
{
if( !obj_ptr )
return ;
if( typeid(*obj_ptr)==typeid(T) )
(((T*)obj_ptr)->*handler)(param);
else
printf("runtime error:get %s while expecting %s\n", typeid(*obj_ptr).name(), typeid(T).name() );
}
{
if( !obj_ptr )
return ;
if( typeid(*obj_ptr)==typeid(T) )
(((T*)obj_ptr)->*handler)(param);
else
printf("runtime error:get %s while expecting %s\n", typeid(*obj_ptr).name(), typeid(T).name() );
}
可以在用g++翻译的时候却被告知:
'void*' is not a pointer-to-object type
看来 void* 是一种无类型的指针,是不允许对其指向的对象做 typeid 操作的。
在一翻冥思苦想后,除了定义一个基类,再也没有找到一个更好的解决办法。假设基类名称为 HandleBase,那么,我们可以让所以包含事件处理函数的类都继承自HandleBase,并对func_call做如下修改:
void func_call(HandleBase* obj_ptr, void* param)
{
if( !obj_ptr )
return ;
if( typeid(*obj_ptr)==typeid(T) )
(((T*)obj_ptr)->*handler)(param);
else
printf("runtime error:get %s while expecting %s\n", typeid(*obj_ptr).name(), typeid(T).name() );
}
{
if( !obj_ptr )
return ;
if( typeid(*obj_ptr)==typeid(T) )
(((T*)obj_ptr)->*handler)(param);
else
printf("runtime error:get %s while expecting %s\n", typeid(*obj_ptr).name(), typeid(T).name() );
}
这样做就绝对没有问题了。话说回来,我之所以不想为包含事件处理函数的类定义一个基类,就是不想增加类定义的复杂性,同时也可以提高事件处理器的通用性,但是,现在为了实现RTTI,却不得不牺牲这个原则了。不过还好,幸好只是继承一个基类而已。
既然增加一个基类已成定局,那么,就让我们好好利用这个基类,来做更多的事情。为了弥补前面说到的typeid中name操作可能不会返回原始类名,那么就让我们在基类里来记录一份原始类名吧,看看类 HandleBase 的定义:
class HandleBase
{
public:
virtual const char* _object_real_type() = 0;
protected:
static const char* check_HandleBase_derived(const char* name) { return name; };
};
{
public:
virtual const char* _object_real_type() = 0;
protected:
static const char* check_HandleBase_derived(const char* name) { return name; };
};
奇怪,为什么不把名称定义成一个成员变量,而要定义成一个纯虚函数呢?还有check_HandleBase_derived,这又是做什么的?别急,我们再来定义一个宏:
#define Registe_Handle_Class(x) \
public: \
const char* _object_real_type() { return x::check_HandleBase_derived(#x); } \
static const char* _st_object_real_type() { return x::check_HandleBase_derived(#x); }
public: \
const char* _object_real_type() { return x::check_HandleBase_derived(#x); } \
static const char* _st_object_real_type() { return x::check_HandleBase_derived(#x); }
呵呵,别怨我卖关子,坚持看完下面ClassA的定义,您一定能明白:
class ClassA : public HandleBase
{
Registe_Handle_Class(ClassA)
//
};
{
Registe_Handle_Class(ClassA)
//
};
完成了!呵呵,怎么样,明白了吗?让我来解释上面的一连串动作吧,我准备采用倒序法来说明。先看ClassA的定义,它继承自HandleBase,原因刚才已经就说过了,至于在类声明的起始处调用宏Registe_Handle_Class,则是对相关操作的一个简化。那么,这个宏都做了哪些操作呢?再来看宏的定义,这时您会发现,这个宏其实就是实现我刚才所说的得到原始类名称的功能。宏的实现中,自动定义了基类HandleBase中的_object_real_type函数,使得外界可以通过_object_real_type调用得到对象的原始类名称。这也就是我为什么不定义一个存储类名的成员变量,而要定义一个纯虚函数的原因:定义成变量,是没办法在派生类的声明中对变量进行赋值的,这样的话,类的实现者就必须手动在类的构造函数中来初始化这个变量。而定义成纯虚函数,就是我心里的小算盘了,你继承了我的类,但是没有调用我的宏,那是编译不过去的!看我是多么的为类的使用者——和我自己——而着想啊!另外还有一个static版的_st_object_real_type,功能是一样的,目的是在没有类对象的情况下,也能够得到类名称。而且,我计划在FuncItem类中的特定地方通过 T::_st_object_real_type 得到原始类名,这样,实例化的FuncItem对象就必须是包含有_st_object_real_type调用的类了,这样的类要么是你自己定义_st_object_real_type(什么人会这么干呢),要么调用Registe_Handle_Class宏,也就是继承自HandleBase,于是,我们事件处理器的安全性又被加强了。
接下来说说check_HandleBase_derived,这看上去似乎是所有定义里最诡异的地方了。其实,我之前已经保证了所有继承自HandleBase的类都必须调用宏Registe_Handle_Class,而check_HandleBase_derived就是为了保证所有调用了宏Registe_Handle_Class的类,都必须继承自HandleBase。因为,如果你没有继承自HandleBase,那么,是找不到check_HandleBase_derived的实现的。同时,check_HandleBase_derived被直接定义在类的声明中,所以,会被看成是inline而直接被函数体所替换,因此,_object_real_type不会因为多调用了一次check_HandleBase_derived而产生更多的栈资源开销。
唉,写了这么多,可是还没有完,因为我们既然已经将类的原始名称记录了下来,那么,就和typeid的name操作说再见吧,修改一下func_call函数:
void func_call(HandleBase* obj_ptr, void* param)
{
if( !obj_ptr )
return ;
if( typeid(*obj_ptr)==typeid(T) )
(((T*)obj_ptr)->*handler)(param);
else
printf("Runtime error:get %s object while expecting %s\n", obj_ptr->_object_real_type(), T::_st_object_real_type());
}
{
if( !obj_ptr )
return ;
if( typeid(*obj_ptr)==typeid(T) )
(((T*)obj_ptr)->*handler)(param);
else
printf("Runtime error:get %s object while expecting %s\n", obj_ptr->_object_real_type(), T::_st_object_real_type());
}
怎么样?万事大吉了吗?没有!别怪没完没了,因为我实在不想把这个话题再延续到下一篇博文了,就让我们在这里一并都解决了吧!
看看您键盘上的Ctrl、C、V是不是磨损最严重的呢?呵呵,干我们这行,这几个键是基本功,不是我不鼓励创新啊,可是有些地方相似的代码直接拷贝过来,是最效率的办法,当然,我们的许多错误,也是这样产生的,就像现在一样!我已经写好了ClassA,它继承了HandleBase,我还需要一个ClassB,也一样要继承自HandleBase,不用多说,拷贝ClassA的一部分定义过来(拷贝多少就看您自己了),改个类名不就完了嘛。呵呵,在此,我估计2/3的朋友会忘记更改Registe_Handle_Class宏参数中的类名称为ClassB!不是我低估您,如果您真的想到了这一点,那您是属于那1/3部分的。虽然这个疏漏并不影响代码运行,但是,真的运行时出错的话,我们可能会被错误提示搞得一头雾水,比如:
Runtime error:get ClassA object while expecting ClassA
为了您着想,我从心眼里想帮您解决这个问题,可是,有办法吗?当然有!我们不是有宏嘛,随便在宏里添加自己的代码,反正使用的人又不知道!那么,就让我们来检查一下这个类的类型吧,修改宏定义如下:
#define Registe_Handle_Class(x) \
public: \
const char* _object_real_type() { return x::check_HandleBase_derived(#x); } \
static const char* _st_object_real_type() { return x::check_HandleBase_derived(#x); } \
private: \
static void _no_use_fun_for_private() {} \
void _test_class_match_ok() { x::_no_use_fun_for_private(); }
public: \
const char* _object_real_type() { return x::check_HandleBase_derived(#x); } \
static const char* _st_object_real_type() { return x::check_HandleBase_derived(#x); } \
private: \
static void _no_use_fun_for_private() {} \
void _test_class_match_ok() { x::_no_use_fun_for_private(); }
前半部分没变,变得是添加了private部分内容。好了,先不分析代码,让我们把刚才说的场景带到这里来试一下。如果在ClassB的定义中,调用了Registe_Handle_Class(ClassA),于是,private部分会被展开成这样:
private: \
static void _no_use_fun_for_private() {} \
void _test_class_match_ok() { ClassA::_no_use_fun_for_private(); }
static void _no_use_fun_for_private() {} \
void _test_class_match_ok() { ClassA::_no_use_fun_for_private(); }
注意,这可是在ClassB的定义中!那么,调用ClassA的私有成员函数就成了一件不可能完成的任务了!
唉,现在真的大功告成了,是不是代码有些乱了?代码有些多了,就不再帖上来,请您点击这里下载最终的代码,并和我一起来检验一下我们的成果吧!
先正常运行一下:
I'm ClassA, show_me is called! param is 0xff00
I'm ClassB, come_on is called! param is 0x0
I'm ClassB, come_on is called! param is 0x0
好,将ClassA的定义中去掉 public HandleBase,保留对宏Registe_Handle_Class的调用,编译出错:
BlogTest.C:81: error: ‘check_HandleBase_derived’ is not a member of ‘ClassA’
再将 public HandleBase 恢复,注释掉对宏Registe_Handle_Class的调用,编译出错:
BlogTest.C:149: error: cannot declare variable ‘ca’ to be of abstract type ‘ClassA’
BlogTest.C:80: note: because the following virtual functions are pure within ‘ClassA’:
BlogTest.C:11: note: virtual const char* HandleBase::_object_real_type()
BlogTest.C:80: note: because the following virtual functions are pure within ‘ClassA’:
BlogTest.C:11: note: virtual const char* HandleBase::_object_real_type()
呵呵,这就验证了Registe_Handle_Class宏和继承HandleBase是必须同时出现的。下面恢复ClassA的正常定义,将ClassB定义中对宏Registe_Handle_Class调用的参数改为 ClassA,编译出错:
BlogTest.C: In member function ‘void ClassB::_test_class_match_ok()’:
BlogTest.C:81: error: ‘static void ClassA::_no_use_fun_for_private()’ is private
BlogTest.C:94: error: within this context
BlogTest.C:81: error: ‘static void ClassA::_no_use_fun_for_private()’ is private
BlogTest.C:94: error: within this context
OK,看来想骗我也是没这么容易的了!再来注释掉ClassB中继承HandleBase的代码和调用宏Registe_Handle_Class的代码,同时注释掉main函数中向ClassB的对象cb发送事件的代码,编译出错:
BlogTest.C:155: instantiated from here
BlogTest.C:58: error: ‘_st_object_real_type’ is not a member of ‘ClassB’
BlogTest.C:58: error: ‘_st_object_real_type’ is not a member of ‘ClassB’
哈哈,太帅了,不继承HandleBase就别想在事件处理器中注册!好,恢复所有的正常代码,现在该验证我们的RTTI了!修改main函数中对发送EVENT_TEST_1事件的处理对象为cb:
send_event(EVENT_TEST_1, /*&ca*/&cb, (void*)0xff00); // modify ca to cb
编译没问题,看运行结果:
Runtime error:get ClassB object while expecting ClassA
I'm ClassB, come_on is called! param is 0x0
I'm ClassB, come_on is called! param is 0x0
哇,我们的目的达到了!看看我们的成果吧,有没有一种满足感呢?
欢迎您把自己的意见写下来,大家一起讨论,如果我的文章能帮上你的忙,我就真得很满足了!
转:http://www.cnblogs.com/Xiao_bird/archive/2009/07/09/1520058.html
0 0
- C++模板实现事件处理器中的“通用成员函数指针”的调用(一)
- C++模板实现事件处理器中的“通用成员函数指针”的调用(一)
- C++模板实现事件处理器中的“通用成员函数指针”的调用(二)
- C++模板实现事件处理器中的“通用成员函数指针”的调用(三)
- C++模板实现事件处理器中的“通用成员函数指针”的调用
- 模板间调用成员函数指针
- 类模板中的函数模板的定义和调用(不是模板类中的普通成员函数)
- 略说成员函数指针及其模板的精简实现
- 成员函数的指针(C++)
- 类的成员指针(对比于c语言中的函数指针)
- 函数指针调用类的成员函数
- 函数指针调用类的成员函数
- 浅析C++中的this指针 通过空指针(NULL)可以正确调用一些类的成员函数?
- 浅析C++中的this指针 通过空指针(NULL)可以正确调用一些类的成员函数?
- 浅析C++中的this指针 通过空指针(NULL)可以正确调用一些类的成员函数?
- 类模板成员函数的调用问题
- 空指针的成员函数调用
- 空指针的成员函数调用
- Spring 文件上传出现400
- 关于单线程生产者消费者模式的一些记录
- javascript和css的入门
- CheckBox复选框
- 第四周项目3 - 单链表应用(2)
- C++模板实现事件处理器中的“通用成员函数指针”的调用(一)
- easyui-textbox设置背景色的问题
- Python 第一课
- Android加载大图片时的OOM异常及解决(图片的二次采样)
- Linux(6)RedHat7 基本命令五-hwclock(clock)命令详解
- 【Bash百宝箱】shell内建命令之冒号
- 在代码中写view 的长宽高等
- 如何调用TUIO中的源码
- 在ubuntu中安装Samba服务器