??? 在 Java 、 C #等語(yǔ)言或者說(shuō)其類(lèi)庫(kù)中,都實(shí)現(xiàn)了事件模型。而 c++ 語(yǔ)言本身并沒(méi)有定義事件機(jī)制,并且在目前眾多優(yōu)秀的 c++ 類(lèi)庫(kù),包括 STL 、 Boost 等都沒(méi)有實(shí)現(xiàn)類(lèi)似的事件機(jī)制。當(dāng)我們被 MFC 的消息搞得頭昏眼花之時(shí),是否有沖動(dòng)自己去實(shí)現(xiàn)一個(gè)簡(jiǎn)單的事件模型呢。我想,有著相同想法的人肯定很多,而真正動(dòng)手來(lái)寫(xiě)可能會(huì)碰到各種各樣的困難。下面就讓我們一步步來(lái)編寫(xiě)一個(gè)簡(jiǎn)單的事件模型。
?
一、 了解事件模型的機(jī)制
在開(kāi)始之前,我們有必要簡(jiǎn)單的了解一下事件模型的機(jī)制。事實(shí)上,事件模型的機(jī)制不止一種, Java 和 c# 的事件機(jī)制就不太一樣,不過(guò)事件模型的基礎(chǔ)都是一樣,那就是一般都使用 Observer 設(shè)計(jì)模式。關(guān)于 Observer 設(shè)計(jì)模式,希望詳細(xì)了解的朋友可以參考《設(shè)計(jì)模式》一書(shū),在這里我們就不詳細(xì)的介紹,只是參照 c# 的事件機(jī)制來(lái)實(shí)現(xiàn)。
在 c# 中,我們可以以 event 關(guān)鍵字聲明一個(gè)事件,然后我們可以將一個(gè)函數(shù)委托掛接在這個(gè)事件上,當(dāng)事件被調(diào)用時(shí),則所有掛接的函數(shù)也會(huì)被調(diào)用。這里有一個(gè)比較難以理解的詞大概是委托,其實(shí)也不難理解。委托其實(shí)就是一種函數(shù)包裝類(lèi),這種類(lèi)可以把任何函數(shù)的指針保存起來(lái),等到需要調(diào)用的時(shí)候再調(diào)用,并且這種類(lèi)的實(shí)例必須能夠掛接到事件之中。
看了上面這段話(huà),是不是覺(jué)得其實(shí)事件模型也并不復(fù)雜。還想對(duì)事件模型了解得更多?我這里就不繼續(xù)了,感興趣的朋友可以查找一下相關(guān)的資料。下面我們就開(kāi)始在 c++ 中編寫(xiě)一個(gè)簡(jiǎn)單的事件模型。
?
二、 設(shè)計(jì)一個(gè)簡(jiǎn)單的 c++ 事件模型
因?yàn)橐粋€(gè)事件模型其實(shí)就是一個(gè)典型的 Observer 設(shè)計(jì)模式,因此最重要的就是 Subject( 目標(biāo)類(lèi) ) 和 Observer( 觀察者類(lèi) ) 的設(shè)計(jì)。
首先,我們需要一個(gè)事件類(lèi),它其實(shí)是一個(gè)抽象的 Subject 。它是一個(gè)基類(lèi),所有的其他事件都從它繼承,并且用戶(hù)只需要從這個(gè)基類(lèi)繼承就可以自定義事件。我們不仿將這個(gè)類(lèi)定義為 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
:
?
????????
//
將偵聽(tīng)的所有函數(shù)或者仿函數(shù)的集合起來(lái)
????????
data_type
m_observer
;
};
?
其次,這個(gè)事件可以?huà)旖尤我獾暮瘮?shù),也就是它可以保存所有的掛接函數(shù)。在 c++ 中,我們大致有三種類(lèi)型的函數(shù),全局或者靜態(tài)函數(shù)、成員函數(shù)、仿函數(shù)。要在 CEvent 中實(shí)現(xiàn)一個(gè)或者多個(gè)方法來(lái)執(zhí)行掛接的任務(wù)不太現(xiàn)實(shí)。我們參考 c# 的實(shí)現(xiàn)方法,可以先實(shí)現(xiàn)一個(gè)委托類(lèi),將各種函數(shù)保存起來(lái)。然后再使用 CEvent 對(duì)這個(gè)委托類(lèi)進(jìn)行掛接。我們不仿稱(chēng)這個(gè)類(lèi)為 CEventHandler 。
然后,我們的這個(gè)委托類(lèi)需要能夠保存所有的函數(shù)。在 c++ 里,我們能夠很容易的得到函數(shù)的指針,只需要在函數(shù)名前加 & ,但是各種函數(shù),特別是成員函數(shù)它是沒(méi)有類(lèi)型的,這就要有一種機(jī)制,將所有的函數(shù)轉(zhuǎn)換為同一種類(lèi)型。因?yàn)楹瘮?shù)是沒(méi)有類(lèi)型,也相當(dāng)于每一個(gè)函數(shù)都是一種類(lèi)型,解決這個(gè)問(wèn)題的方法其實(shí)就是使用模板類(lèi)。因此,我們先定義一個(gè)虛基類(lèi) CFunImpl 。對(duì)三種不同的函數(shù),我們分別從 CfunImpl 繼承三種不同的子類(lèi), CstaticFun 、 CmemFun,CFunctor 。 CstaticFun 表示全局的或者靜態(tài)的函數(shù),不過(guò)這必須是一個(gè)模板類(lèi),對(duì)于每一個(gè)靜態(tài)或者全局函數(shù),都是模板類(lèi)的一個(gè)特化。類(lèi)聲明的代碼如下:
template
<
typename
Fun
>
????????
class
CStaticFun
:
public
CFunImpl
???????? {
????????
public
:
?
????????
????????
CStaticFun
(
const
Fun
&
fun
) :
m_fun
(
fun
){};
??????????????????
??????????????????
virtual
~
CStaticFun
()
?????????????????? {
?????????????????? }
????????
protected
:
??????????????????
Fun
m_fun
;
???????? };
?
對(duì)于 CmemFun 類(lèi),我們?nèi)绶ㄅ谥疲贿^(guò)我們這時(shí)需要保存兩個(gè)成員變量,一個(gè)是成員函數(shù)的指針,另一個(gè)則是該成員函數(shù)所屬的對(duì)象的指針。類(lèi)聲明的代碼如下:
?
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
;
};
?
對(duì)于仿函數(shù),我們這里先不講,因?yàn)榉潞瘮?shù)使用得很少,并且這還涉及到仿函數(shù)的概念,有興趣的朋友可以在閱讀完本文之后自己來(lái)實(shí)現(xiàn)。
好,到現(xiàn)在為止,我們可以將所有的函數(shù)使用 CfunImpl 來(lái)表示了。因此,我們可以在委托類(lèi) CEventHandler 中保存 CfunImpl* 來(lái)達(dá)到我們的目的。 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
;
?????????????????? }
??????????????????
??????????????????
//
成員函數(shù)
????????
????????
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
;
?
???????? };
?
不過(guò)要實(shí)現(xiàn)事件機(jī)制,我們還需要 CfunImpl* 的子類(lèi)必須實(shí)現(xiàn)幾個(gè)方法。
第一個(gè)方法就是調(diào)用函數(shù)的方法,我們這里要求重載 void operator()(CEvent& e); 來(lái)達(dá)到目的。
第二個(gè)方法就是 operator== ,為什么需要這個(gè)方法呢。因?yàn)閷?duì)于 CEvent 來(lái)說(shuō),調(diào)用 UnRegister() 時(shí),我們必須找到我們使用 Register() 添加到 list 中的函數(shù),這時(shí)我們需要判斷兩個(gè) CEventHandler 對(duì)象是否相等,這是件麻煩事情。 CEventHandler 保存的是 CFunImpl 的指針,好吧,這個(gè)我們就要求 CfunImpl 的子類(lèi)必須實(shí)現(xiàn)重載 operator== 。
第三個(gè)方法,從 CfunImpl 繼承必須實(shí)現(xiàn) Clone() 方法。 Clone() 方法可以讓 CEventHandler 實(shí)現(xiàn)拷貝。具體的實(shí)現(xiàn)方法我們可以查看源碼。
?
三、 使用事件模型
一個(gè)簡(jiǎn)單的事件模型基本完成了。現(xiàn)在我們可以編寫(xiě)一個(gè)例子來(lái)演示一下如何來(lái)使用這個(gè)事件模型。其實(shí)使用起來(lái)很簡(jiǎn)單。
我們可以聲明一個(gè) CEvent 或者 CEvent 子類(lèi)的實(shí)例。讓需要監(jiān)聽(tīng)的函數(shù)掛接到這個(gè) CEvent 中,我們實(shí)現(xiàn)了 Register() 函數(shù)和 += 操作符,都可以使用。掛接的語(yǔ)句就像這樣:
CEvent evt;
CobserverTest obj;
evt += CEventHandler (& obj , &( CObserverTest :: OnStartEvent ));
當(dāng)事件的 operator() 方法調(diào)用時(shí),就會(huì)自動(dòng)調(diào)用我們的監(jiān)聽(tīng)函數(shù)。具體的例子可以查看提供的源代碼。
?
四、 模型的缺陷和改進(jìn)
到現(xiàn)在為止,我們終于實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的事件模型。它可以有效的工作,并且與平臺(tái)無(wú)關(guān)。當(dāng)然,要求你的編譯器盡量的支持 c++ 標(biāo)準(zhǔn)。如果你的編譯器對(duì) c++98 標(biāo)準(zhǔn)支持不夠,也許它不能夠工作正常。筆者在 VC6SP6 和 VC2005 中測(cè)試均通過(guò)。
不過(guò)這個(gè)模型還有可以改進(jìn)的余地。其一、目前只是支持同步操作,即當(dāng)你的事件進(jìn)行調(diào)用時(shí)是在同一線程里順序進(jìn)行。要改進(jìn)到支持異步調(diào)用就必須在每次調(diào)用時(shí)在一個(gè)新的線程中進(jìn)行調(diào)用。因此我們需要一個(gè)線程機(jī)制來(lái)完成這個(gè)工作。如果在一個(gè)特定的平臺(tái)中使用,如 VC 中,我們可以在響應(yīng)函數(shù)中創(chuàng)建一個(gè)新的線程。
其二、目前的所有的函數(shù)參數(shù)必須為 (CEvent& e) ,不能寫(xiě)成 (CchildEvent& e) 。要對(duì)這個(gè)限制進(jìn)行改進(jìn),就需要編譯器支持虛模板成員函數(shù)的機(jī)制。不過(guò)在 VC6 和 VC2005 中,均不支持這種特性。因此要么尋求它法,要么就等到編譯器的支持吧。
其三、線程安全,這個(gè)簡(jiǎn)單的事件模型使用到了 STL 中的 list 組件,但是并非所有的 list 實(shí)現(xiàn)都是線程安全的。建議大家采用 stlport ,因?yàn)樗膶?shí)現(xiàn)是線程安全的,如果沒(méi)有采用線程安全的實(shí)現(xiàn),那么在調(diào)用 Register 和 UnRegister 時(shí)最好能夠?qū)崿F(xiàn)互斥。
?
限于筆者知識(shí)水平和文筆有限,不當(dāng)之處肯定很多。若你有好的想法和建議,可以 email 告訴我。本文中所涉及到的所有源碼可以從這里下載http://m.shnenglu.com/Files/lunny/CEventModel.rar 。
?
????????????? ???????????????????? ???????????????????? ?????? ????????????? ???????????????????? ????????????? 肖倫文
xiaolunwen@hotmail.com