在
Java
、
C
#等语言或者说其类库中,都实现了事件模型。而
c++
语言本身并没有定义事件机制,并且在目前众多优秀的
c++
类库,包括
STL
、
Boost
等都没有实现类似的事件机制。当我们被
MFC
的消息搞得头昏眼花之时,是否有冲动自己去实现一个简单的事件模型呢。我想,有着相同想法的人肯定很多,而真正动手来写可能会碰到各种各样的困难。下面就让我们一步步来编写一个简单的事件模型。
一、
了解事件模型的机制
在开始之前,我们有必要简单的了解一下事件模型的机制。事实上,事件模型的机制不止一种,
Java
和
c#
的事件机制就不太一样,不过事件模型的基础都是一样,那就是一般都使用
Observer
设计模式。关于
Observer
设计模式,希望详细了解的朋友可以参考《设计模式》一书,在这里我们就不详细的介绍,只是参照
c#
的事件机制来实现。
在
c#
中,我们可以以
event
关键字声明一个事件,然后我们可以将一个函数委托挂接在这个事件上,当事件被调用时,则所有挂接的函数也会被调用。这里有一个比较难以理解的词大概是委托,其实也不难理解。委托其实就是一种函数包装类,这种类可以把任何函数的指针保存起来,等到需要调用的时候再调用,并且这种类的实例必须能够挂接到事件之中。
看了上面这段话,是不是觉得其实事件模型也并不复杂。还想对事件模型了解得更多?我这里就不继续了,感兴趣的朋友可以查找一下相关的资料。下面我们就开始在
c++
中编写一个简单的事件模型。
二、
设计一个简单的
c++
事件模型
因为一个事件模型其实就是一个典型的
Observer
设计模式,因此最重要的就是
Subject(
目标类
)
和
Observer(
观察者类
)
的设计。
首先,我们需要一个事件类,它其实是一个抽象的
Subject
。它是一个基类,所有的其他事件都从它继承,并且用户只需要从这个基类继承就可以自定义事件。我们不仿将这个类定义为
CEvent
。
CEvent
的声明如下:
class
CEvent
{
public
:
typedef
list
<
CEventHandler
>
data_type
;
CEvent
();
virtual
~
CEvent
();
void
operator
()()
{
data_type
::
iterator
it
;
for
(
it
=
m_observer
.
begin
();
it
!=
m_observer
.
end
(); ++
it
)
{
(*
it
)(*
this
);
}
}
CEvent
&
operator
+= (
const
CEventHandler
&
handler
)
{
Register
(
handler
);
return
*
this
;
}
CEvent
&
operator
-= (
const
CEventHandler
&
handler
)
{
UnRegister
(
handler
);
return
*
this
;
}
void
Register
(
const
CEventHandler
&
handler
)
{
m_observer
.
push_back
(
handler
);
}
void
UnRegister
(
const
CEventHandler
&
handler
)
{
m_observer
.
remove
(
handler
);
}
protected
:
//
将侦听的所有函数或者仿函数的集合起来
data_type
m_observer
;
};
其次,这个事件可以挂接任意的函数,也就是它可以保存所有的挂接函数。在
c++
中,我们大致有三种类型的函数,全局或者静态函数、成员函数、仿函数。要在
CEvent
中实现一个或者多个方法来执行挂接的任务不太现实。我们参考
c#
的实现方法,可以先实现一个委托类,将各种函数保存起来。然后再使用
CEvent
对这个委托类进行挂接。我们不仿称这个类为
CEventHandler
。
然后,我们的这个委托类需要能够保存所有的函数。在
c++
里,我们能够很容易的得到函数的指针,只需要在函数名前加
&
,但是各种函数,特别是成员函数它是没有类型的,这就要有一种机制,将所有的函数转换为同一种类型。因为函数是没有类型,也相当于每一个函数都是一种类型,解决这个问题的方法其实就是使用模板类。因此,我们先定义一个虚基类
CFunImpl
。对三种不同的函数,我们分别从
CfunImpl
继承三种不同的子类,
CstaticFun
、
CmemFun,CFunctor
。
CstaticFun
表示全局的或者静态的函数,不过这必须是一个模板类,对于每一个静态或者全局函数,都是模板类的一个特化。类声明的代码如下:
template
<
typename
Fun
>
class
CStaticFun
:
public
CFunImpl
{
public
:
CStaticFun
(
const
Fun
&
fun
) :
m_fun
(
fun
){};
virtual
~
CStaticFun
()
{
}
protected
:
Fun
m_fun
;
};
对于
CmemFun
类,我们如法炮制,不过我们这时需要保存两个成员变量,一个是成员函数的指针,另一个则是该成员函数所属的对象的指针。类声明的代码如下:
template
<
typename
PointerToObj
,
typename
PointerToMemFun
>
class
CMemFun
:
public
CFunImpl
{
public
:
CMemFun
(
const
PointerToObj
&
pObj
,
PointerToMemFun
pMemFn
)
:
m_pObj
(
pObj
),
m_pMemFun
(
pMemFn
)
{
}
virtual
~
CMemFun
()
{
}
protected
:
PointerToObj
m_pObj
;
PointerToMemFun
m_pMemFun
;
};
对于仿函数,我们这里先不讲,因为仿函数使用得很少,并且这还涉及到仿函数的概念,有兴趣的朋友可以在阅读完本文之后自己来实现。
好,到现在为止,我们可以将所有的函数使用
CfunImpl
来表示了。因此,我们可以在委托类
CEventHandler
中保存
CfunImpl*
来达到我们的目的。
CEventHandler
的声明如下:
class
CEventHandler
{
public
:
virtual
~
CEventHandler
()
{
Clear
();
}
template
<
class
Fun
>
CEventHandler
(
const
Fun
&
fun
)
:
m_pImpl
(
new
CStaticFun
<
Fun
>(
fun
))
{}
CEventHandler
(
const
CEventHandler
&
fun
)
:
m_pImpl
(
NULL
)
{
*
this
=
fun
;
}
void
Clear
()
{
if
(
m_pImpl
)
{
delete
m_pImpl
;
m_pImpl
=
NULL
;
}
}
CEventHandler
&
operator
= (
const
CEventHandler
&
fun
)
{
Clear
();
if
(
fun
.
m_pImpl
)
{
m_pImpl
=
fun
.
m_pImpl
->
Clone
();
}
return
*
this
;
}
//
成员函数
template
<
typename
PointerToObj
,
typename
PointerToMemFun
>
CEventHandler
(
const
PointerToObj
&
pObj
,
const
PointerToMemFun
&
pMemFun
)
:
m_pImpl
(
new
CMemFun
<
PointerToObj
,
PointerToMemFun
>(
pObj
,
pMemFun
))
{}
void
operator
()(
CEvent
&
e
)
{
if
(
m_pImpl
)
{
(*
m_pImpl
)(
e
);
}
}
bool
operator
== (
const
CEventHandler
&
handler
)
{
if
(
m_pImpl
==
NULL
||
handler
.
m_pImpl
==
NULL
)
{
return
true
;
}
if
(
typeid
(
m_pImpl
) ==
typeid
(
handler
.
m_pImpl
))
{
return
(*
m_pImpl
) == (*(
handler
.
m_pImpl
));
}
return
false
;
}
protected
:
CFunImpl
*
m_pImpl
;
};
不过要实现事件机制,我们还需要
CfunImpl*
的子类必须实现几个方法。
第一个方法就是调用函数的方法,我们这里要求重载
void operator()(CEvent& e);
来达到目的。
第二个方法就是
operator==
,为什么需要这个方法呢。因为对于
CEvent
来说,调用
UnRegister()
时,我们必须找到我们使用
Register()
添加到
list
中的函数,这时我们需要判断两个
CEventHandler
对象是否相等,这是件麻烦事情。
CEventHandler
保存的是
CFunImpl
的指针,好吧,这个我们就要求
CfunImpl
的子类必须实现重载
operator==
。
第三个方法,从
CfunImpl
继承必须实现
Clone()
方法。
Clone()
方法可以让
CEventHandler
实现拷贝。具体的实现方法我们可以查看源码。
三、
使用事件模型
一个简单的事件模型基本完成了。现在我们可以编写一个例子来演示一下如何来使用这个事件模型。其实使用起来很简单。
我们可以声明一个
CEvent
或者
CEvent
子类的实例。让需要监听的函数挂接到这个
CEvent
中,我们实现了
Register()
函数和
+=
操作符,都可以使用。挂接的语句就像这样:
CEvent evt;
CobserverTest obj;
evt
+=
CEventHandler
(&
obj
, &(
CObserverTest
::
OnStartEvent
));
当事件的
operator()
方法调用时,就会自动调用我们的监听函数。具体的例子可以查看提供的源代码。
四、
模型的缺陷和改进
到现在为止,我们终于实现了一个简单的事件模型。它可以有效的工作,并且与平台无关。当然,要求你的编译器尽量的支持
c++
标准。如果你的编译器对
c++98
标准支持不够,也许它不能够工作正常。笔者在
VC6SP6
和
VC2005
中测试均通过。
不过这个模型还有可以改进的余地。其一、目前只是支持同步操作,即当你的事件进行调用时是在同一线程里顺序进行。要改进到支持异步调用就必须在每次调用时在一个新的线程中进行调用。因此我们需要一个线程机制来完成这个工作。如果在一个特定的平台中使用,如
VC
中,我们可以在响应函数中创建一个新的线程。
其二、目前的所有的函数参数必须为
(CEvent& e)
,不能写成
(CchildEvent& e)
。要对这个限制进行改进,就需要编译器支持虚模板成员函数的机制。不过在
VC6
和
VC2005
中,均不支持这种特性。因此要么寻求它法,要么就等到编译器的支持吧。
其三、线程安全,这个简单的事件模型使用到了
STL
中的
list
组件,但是并非所有的
list
实现都是线程安全的。建议大家采用
stlport
,因为它的实现是线程安全的,如果没有采用线程安全的实现,那么在调用
Register
和
UnRegister
时最好能够实现互斥。
限于笔者知识水平和文笔有限,不当之处肯定很多。若你有好的想法和建议,可以
email
告诉我。本文中所涉及到的所有源码可以从这里下载http://www.cppblog.com/Files/lunny/CEventModel.rar
。
肖伦文
xiaolunwen@hotmail.com
posted on 2006-07-30 20:48
风间苍月 阅读(4234)
评论(6) 编辑 收藏 引用