|
Posted on 2009-11-22 02:36 cexer 閱讀(3743) 評論(40) 編輯 收藏 引用 所屬分類: GUI
轉(zhuǎn)帖請注明出處 http://www.shnenglu.com/cexer/archive/2009/11/22/101591.html
1 胸口碎大石
緊接上話:GUI框架:談?wù)効蚣埽瑢憣懘a 。廢話是肯定首先要說的,既為了承前啟后點(diǎn)明主題,也為了拉攏人心騙取回復(fù)。本來我想像自己上篇博文寫出來勢必像胸口碎大石一樣威猛有力,在街邊拉開陣勢,大吼一聲舉起錘子正要往下砸的時候,卻看到幾位神仙手提醬油瓶優(yōu)雅地踏著凌波微步路過,聽他們開口閉口說的都是六脈神劍啊,九陰真級啊這些高級東西,我的威猛感一下消失于無形,取而代之的是小孩子玩水槍的渺小。但是不管怎么樣攤子都鋪開了,這一錘子不砸下去,對不起那涼了半天石頭的胸肌。
在此之前首先得感謝一下各位醬油眾。無論你們是看熱鬧的還是砸場子的,你們的圍觀都令我的博文增光不少。特別要感謝那幾位打架的神仙,你們使上篇博文真正變得有思想交鋒的精彩。我覺得你們的那些想法和爭論都非常有價值,建議你們不要只讓它們在這個角落里藏著,都寫到自己的博客上去讓更多的人看到吧。
走過路過不要錯過,有錢的捧個錢場,沒錢的繼續(xù)揮舞你的醬油瓶加油吶喊,我這一錘要砸下去了!
2 實(shí)現(xiàn)消息檢查者
上文將消息框架分為幾個部分,這篇博文實(shí)現(xiàn)其中的消息檢查者。經(jīng)典的用 API 編寫 GUI 程序的方式當(dāng)中,消息檢查都是用 if 或者 switch 語句進(jìn)行的:
1: // 經(jīng)典的 API 方式
2: switch( message )
3: {
4: case WM_CREATE:
5: // ......
6: break;
7: case WM_PAINT:
8: // ......
9: break;
10: default:
11: // ......
12: }
13:
14: // MFC 映射宏展開
15: if ( message == WM_CREATE )
16: {
17: // ......
18: }
19: if ( message == WM_PAINT )
20: {
21: // ......
22: }
見過的很多的 GUI 框架并沒有在這原始的方式上進(jìn)步多少,"只是將黑換成暗"。比如 MFC 和 WTL 的消息映射宏,就像是披在 if 語句上的皇帝的新衣。這種消息檢查方式的好處是速度快,不用額外的空間消耗,但壞處更明顯:不容易擴(kuò)充。我覺得在好處和壞處之間的取舍很容易,有必要單獨(dú)給消息檢查的過程實(shí)現(xiàn)一個更具 OO 含義的執(zhí)行者:消息檢查者(MessageChecker )。
2.1 其實(shí)很簡單
要有消息檢查者,首先得有個消息(Message)。上篇博文中的消息定義雖然非常簡單,卻完全可以勝任目前需要的工作,因此我們直接復(fù)制過來。
1: typedef LRESULT MessageResult;
2: typedef UINT MessageId;
3: typedef WPARAM MessageWparam;
4: typedef LPARAM MessageLparam;
5:
6: // 簡單的消息定義
7: class Message
8: {
9: public:
10: Message( MessageId id_=0,MessageWparam wp_=0,MessageLparam lp_=0 )
11: :id( id_ )
12: ,wparam( wp_ )
13: ,lparam( lp_ )
14: ,result( 0 )
15: {}
16:
17: public:
18: MessageResult result;
19: MessageId id;
20: MessageWparam wparam;
21: MessageLparam lparam;
22: };
有了消息,現(xiàn)在開始定義消息檢查者。消息檢查者的職責(zé):根據(jù)某種條件檢查消息并返回(是,否)的檢查結(jié)果,所以它既應(yīng)該有用于檢查的數(shù)據(jù),也應(yīng)該有用于檢查的動作函數(shù),并且該函數(shù)檢查返回布爾值。這不是很容易就出來了嗎:
1: // 領(lǐng)銜的消息檢查者
2: class MessageChecker
3: {
4: public:
5: MessageChecker( MessageId id_=0,MessageWparam wp_=0,MessageLparam lp_=0 )
6: :id( id_ )
7: ,wparam( wp_ )
8: ,lparam( lp_ )
9: {}
10:
11: public:
12: // 用于檢查消息的函數(shù)
13: virtual bool isOk( const Message& message ) const;
14:
15: public:
16: // 用于檢查消息的數(shù)據(jù)
17: MessageId id;
18: MessageWparam wparam;
19: MessageLparam lparam;
20: };
其中 MessageChecker::isOk 很明顯應(yīng)該可以被后繼者重寫以實(shí)現(xiàn)不同的檢查方式,所以它應(yīng)該是虛函數(shù),這樣后繼者可以這樣的形式擴(kuò)充檢查者隊(duì)伍:
1: // 命令消息檢查者
2: class CommandChecker:public MessageChecker
3: {
4: public:
5: virtual bool isOk( const Message& message );
6: };
7:
8: // 通知消息檢查者
9: class NotifyChecker:public MessageChecker
10: {
11: public:
12: virtual bool isOk( const Message& message );
13: };
看著 MessageChecker,CommandChecker,NotifyChecker 這些和諧的名字,感覺消息檢查者就這樣實(shí)現(xiàn)完成了,我們的 GUI 框架似乎已經(jīng)成功邁出了重要的第一步。但是面對函數(shù) isOk ,有經(jīng)驗(yàn)的程序員肯定會有疑問:真的這樣簡單就 ok 了?當(dāng)然是 no。要是真有那么簡單,我何苦還在后面寫那么長的篇符呢,cppblog 又不能多寫字騙稿費(fèi)的。
2.2 堆上生成 & 對象切割
看著虛函數(shù) isOk 會想聯(lián)到兩個關(guān)鍵詞:多態(tài),指針。多態(tài)意味著要保存為指針,指針意味著要在堆上生成。消息檢查者保存為指針的映射像下面這樣子(假設(shè)消息處理者叫做 MessageHandler ):
1: // 允許一個消息對應(yīng)多個處理者
2: typedef vector<MessageHandler> _HandlerVector;
3:
4: // 為了多態(tài)必須保存 MessageChecker 的指針
5: typedef pair<MessageChecker*,_HandlerVector> _HandlerPair;
6:
7: // 消息映射
8: typedef vector<_HandlerPair> _HandlerMap;
堆上生成是萬惡之源。誰來負(fù)責(zé)銷毀?何時銷毀?效率問題怎么辦?有的人此時可能想到了引用計數(shù),小對象分配技術(shù),內(nèi)存池。。。只是一個消息檢查的動作就用那么昂貴的實(shí)現(xiàn),就像花兩萬塊買張鼠標(biāo)墊一樣讓人難以接受。所以我們想保存消息映射者的對象 MessageChecker 而不是它的指針,就像這個樣子:
1: // 允許一個消息對應(yīng)多個處理者
2: typedef vector<MessageHandler> _HandlerVector;
3:
4: // 保存 MessageChecker 對象而不是指針
5: typedef pair<MessageChecker,_HandlerVector> _HandlerPair;
6:
7: // 消息映射
8: typedef vector<_HandlerPair> _HandlerMap;
但這樣的保存方式帶來了一個新的問題:對象切割。如果往映射中放入派生類的對象比如 CommandChecker 或者 NotifyChecker,編譯器會鐵手無情地對它們進(jìn)行切割,切割得它們體無完膚搖搖欲墜,只剩下 MessageChecker 子對象為止 。砍頭不要緊,只要主義真,但是要是切割過程中切掉了虛函數(shù)表這個命根子就完蛋了,在手執(zhí)電鋸的編譯器面前玩耍虛函數(shù),很難說會發(fā)生什么可怕的事情。
有一種解決方案是我們不使用真正的虛函數(shù),而是自己模擬虛函數(shù)的功能。具體辦法是在 MessageChecker 當(dāng)中保存一個函數(shù)指針,由子類去把它指向自己實(shí)現(xiàn)的函數(shù),非虛的 MessageChecker::isOk 函數(shù)去調(diào)用這個指針。修改 MessageChecker 的定義:
1: // 領(lǐng)銜的消息檢查者,用函數(shù)指針模擬虛函數(shù)
2: class MessageChecker
3: {
4: // 模擬虛函數(shù)的函數(shù)指針類型
5: typedef bool (*_VirtualIsOk)( const MessageChecker* pthis,const Message& message );
6:
7: public:
8: MessageChecker( MessageId id_=0,MessageWparam wp_=0,MessageLparam lp_=0,_VirtualIsOk is_=&MessageChecker::virtualIsOk )
9: :id( id_ )
10: ,wparam( wp_ )
11: ,lparam( lp_ )
12: ,m_visok( is_ )
13: {}
14:
15: public:
16: // 非虛函數(shù)調(diào)用函數(shù)指針
17: bool isOk( const Message& message ) const
18: {
19: return m_visok( this,message );
20: }
21:
22: protected:
23: // 不騙你,我真的是虛函數(shù)
24: static bool virtualIsOk( const MessageChecker* pthis,const Message& message )
25: {
26: return pthis->id == message.id;
27: }
28:
29: public:
30: // 用于檢查消息的數(shù)據(jù)
31: MessageId id;
32: MessageWparam wparam;
33: MessageLparam lparam;
34:
35: protected:
36: // 模擬虛函數(shù)的函數(shù)指針
37: _VirtualIsOk m_visok;
38: };
如代碼所示的,MessageChecker::virtualIsOk 的默認(rèn)實(shí)現(xiàn)是只檢查消息id,這也是 MFC 的映射宏 MESSAGE_HANDLER 干的事情。現(xiàn)在解決了不要堆生成與要求多態(tài)的矛盾關(guān)系,真正完成了消息檢查者的定義。
2.3 擴(kuò)充隊(duì)伍
要擴(kuò)展消息檢查者隊(duì)伍,可以從 MessageChecker 派生新類,定義自己的檢查函數(shù)(virtualIsOk 或者其它名字都可以)并將 MessageChecker::m_visok 指向這個函數(shù)。要注意的是因?yàn)榇嬖趯ο笄懈顔栴},所以派生類不應(yīng)該定義新的數(shù)據(jù)成員,畢竟切掉花花草草也是非常不好的。舉例說明派生方法。
比如從消息檢查者派生一個命令消息(Command)的檢查者 CommandChecker:它定義了一個函數(shù) virtualIsOk ,此函數(shù)檢查消息id是否為 WM_COMMAND ,并進(jìn)一步檢查控件id和命令code,然后將指針 Message::m_visok 指向這個函數(shù) CommandChecker::virualIsOk,這樣 MessageChecker::isOk 實(shí)際上就是調(diào)用的 CommandChecker::virtualIsOk 了。CommandChecker 的功能類似于 MFC 的宏 COMMAND_HANDLER:
1: // 命令消息檢查者
2: class CommandChecker:public MessageChecker
3: {
4: public:
5: // 將函數(shù)指針指向自己的函數(shù) CommandChecker;:virtualIsOk
6: CommandChecker( WORD id,WORD code )
7: :MessageChecker( WM_COMMAND,MAKEWPARAM(id,code),0,&CommandChecker::virtualIsOk )
8: {}
9:
10: protected:
11: // 檢查消息id是否為 WM_COMMAND,并進(jìn)一步檢查控件id和命令code
12: static bool virtualIsOk( const MessageChecker* pthis,const Message& message )
13: {
14: WORD idToCheck = LOWORD( message.wparam );
15: WORD codeToCheck = HIWORD( message.wparam );
16:
17: WORD id = LOWORD( pthis->wparam );
18: WORD code = HIWORD( pthis->wparam );
19:
20: return message.id==WM_COMMAND && idToCheck==id && codeToCheck==code;
21: }
22: };
同理定義一個通知消息(Notification)的檢查者 NotifyChecker,其功能類似于 MFC 的宏 NOTIFY_HANDLER :
1: // 通知消息檢查者
2: class NotifyChecker:public MessageChecker
3: {
4: public:
5: // 將函數(shù)指針指向自己的函數(shù) NotifyChecker;:virtualIsOk
6: NotifyChecker( WORD id,WORD code )
7: :MessageChecker( WM_NOTIFY,MAKEWPARAM(id,code),0,&NotifyChecker::virtualIsOk )
8: {}
9:
10: public:
11: // 檢查消息的 id 是否為 WM_NOTIFY ,并進(jìn)一步檢查控件 id 和命令 code
12: static bool virtualIsOk( const MessageChecker* pthis,const Message& message )
13: {
14: WORD idToCheck = reinterpret_cast<NMHDR*>(message.lparam)->idFrom;
15: WORD codeToCheck = reinterpret_cast<NMHDR*>(message.lparam)->code;
16:
17: WORD id = LOWORD( pthis->wparam );
18: WORD code = HIWORD( pthis->wparam );
19:
20: return message.id==WM_NOTIFY && idToCheck==id && codeToCheck==code;
21: }
22: };
發(fā)揮想像進(jìn)行擴(kuò)展,這個消息檢查者可以做很多事情。比如定義一個范圍id內(nèi)命令消息的檢查者 RangeIdCommandChecker,其功能類似于 MFC 的 ON_COMMAND_RANGE 宏:
1: // 范圍 id 命令消息檢查者
2: class RangeIdCommandChecker:public MessageChecker
3: {
4: public:
5: // 將函數(shù)指針指向自己的函數(shù) RangeIdCommandChecker;:virtualIsOk
6: RangeIdCommandChecker( WORD idMin,WORD idMax,WORD code )
7: :MessageChecker( WM_COMMAND,MAKEWPARAM(idMin,idMax),MAKELPARAM(code,0),&RangeIdCommandChecker::virtualIsOk )
8: {}
9:
10: protected:
11: // 檢查消息 id 是否為 WM_COMMAND,并進(jìn)一步檢查控件 id 范圍和命令 code
12: static bool virtualIsOk( const MessageChecker* pthis,const Message& message )
13: {
14: WORD idToCheck = LOWORD( message.wparam );
15: WORD codeToCheck = HIWORD( message.wparam );
16:
17: WORD idMin = LOWORD( pthis->wparam );
18: WORD idMax = HIWORD( pthis->wparam );
19: WORD code = LOWORD( pthis->lparam );
20:
21: return message.id==WM_COMMAND && codeToCheck==code && idToCheck>=idMin && idToCheck<=idMax;
22: }
23: };
定義一個按鈕點(diǎn)擊消息的消息檢查者:
1: // 按鈕點(diǎn)擊的命令消息檢查者
2: class ButtonClickingChecker:public MessageChecker
3: {
4: public:
5: // 將函數(shù)指針指向自己的函數(shù) ButtonClickChecker;:virtualIsOk
6: ButtonClickingChecker( WORD id )
7: :MessageChecker( WM_COMMAND,MAKEWPARAM(id,BN_CLICKED),0,&ButtonClickingChecker::virtualIsOk )
8: {}
9:
10: protected:
11: // 檢查消息id是否為 WM_COMMAND,并進(jìn)一步檢查命令code是否為 BN_CLICKED 以及按鈕id
12: static bool virtualIsOk( const MessageChecker* pthis,const Message& message )
13: {
14: WORD idToCheck = LOWORD( message.wparam );
15: WORD codeToCheck = HIWORD( message.wparam );
16:
17: WORD id = LOWORD( pthis->wparam );
18: WORD code = BN_CLICKED;
19:
20: return message.id==WM_COMMAND && idToCheck==id && codeToCheck==code;
21: }
22: };
2.4 數(shù)據(jù)不夠用
這一節(jié)是新加的。因?yàn)橛腥颂岢鰯?shù)據(jù)容納不下的問題:MessageChecker 目前的定義只能容納 WPAWAM,LPARAM 兩個參數(shù)大小的數(shù)據(jù),而因?yàn)樵诖嬖趯ο笄懈顔栴},子類又不能定義新的數(shù)據(jù),那如果 WPARAM,LPARAM 裝不下需要的數(shù)據(jù)了怎么辦?難道就只能把數(shù)據(jù)在子類當(dāng)中定義,然后每次都在堆上生成 MessageChecker 嗎?
我在這里的解決方案是,在 MessageChecker 里面定義一個多態(tài)的數(shù)據(jù)成員,這個成員大多數(shù)時候是空的,當(dāng)需要的時候從堆上生成它,用它來裝下數(shù)據(jù)。如代碼所示:
先定義一個多態(tài)的數(shù)據(jù)類 MessageData:
1: // 消息數(shù)據(jù)
2: class MessageData
3: {
4: public:
5: virtual ~MessageData(){}
6: virtual MessageData* clone() const = 0;
7: virtual void release(){ delete this; }
8: };
在 MessageChecker 當(dāng)中加入這個數(shù)據(jù)成員:
1: // 加入多態(tài)的數(shù)據(jù)成員
2: class MessageChecker
3: {
4: //......
5: public:
6: MessageData* data;
7: }
8:
9: // 拷貝構(gòu)造時同時深拷貝數(shù)據(jù)
10: MessageChecker::MessageChecker( const MessageChecker& other )
11: :id( other.id )
12: ,wparam( other.wparam )
13: ,lparam( other.lparam )
14: ,m_visok( other.m_visok )
15: ,data( other.data?other.data->clone():NULL )
16: {}
17:
18: // 拷貝時同時深拷貝數(shù)據(jù)
19: MessageChecker& MessageChecker::operator=( const MessageChecker& other )
20: {
21: if ( this != &other )
22: {
23: id = other.id;
24: wparam = other.wparam;
25: lparam = other.lparam;
26: m_visok = other.m_visok;
27:
28: if ( data )
29: {
30: data->release();
31: data = NULL;
32: }
33:
34: if ( other.data )
35: {
36: data = other.data->clone();
37: }
38: }
39: return *this;
40: }
41:
42: // 析構(gòu)時刪除
43: MessageChecker::~MessageChecker()
44: {
45: if ( data )
46: {
47: data->release();
48: data = NULL;
49: }
50: }
舉例說明怎么樣使用 data 成員。例如要在一個消息檢查者需要比較字符串,則在其中必須要保存供比較的字符串。先從 MessageData 派生一個保存字符串的數(shù)據(jù)類 MessageString:
1: // 裝字符串多態(tài)數(shù)據(jù)類
2: class MessageString:public MessageData
3: {
4: public:
5: MessageString( const String& string )
6: :content( string )
7: {}
8:
9: virtual MessageData* clone() const
10: {
11: return new MessageString( content );
12: }
13:
14: public:
15: String content;
16: };
然后在這個消息檢查者中可以使用這個類了:
1: // 檢查字符串的消息檢查者
2: class StringMessageChecker:public MessageChecker
3: {
4: public:
5: StringMessageChecker( const String& string )
6: :MessageChecker( WM_SETTEXT,0,0,&StringMessageChecker::virtualIsOk )
7: {
8: data = new MessageString( string );
9: }
10:
11: public:
12: static bool virtualIsOk( const MessageChecker* pthis,const Message& message )
13: {
14: if ( message.id != pthis->id )
15: {
16: return false;
17: }
18:
19: MessageString* data = (MessageString*)pthis->data;
20: if ( !data )
21: {
22: return false;
23: }
24:
25: std::string stirngToCheck = (const Char*)( message.lparam );
26: return stirngToCheck == data->content;
27: }
28: };
為了使用方便可以定義一個數(shù)據(jù)類的模板:
1: // 數(shù)據(jù)類的模板
2: template <typename TContent>
3: class MessageDataT:public MessageData
4: {
5: public:
6: MessageDataT( const TContent& content_ )
7: :content( content_ )
8: {}
9:
10: virtual MessageData* clone() const
11: {
12: return new MessageDataT( content );
13: }
14:
15: public:
16: TContent content;
17: };
然后可以將字符串?dāng)?shù)據(jù)類的定義簡化為:
1: // 利用模板生成數(shù)據(jù)類
2: typedef MessageDataT<String> MessageString;
2.5 龍?zhí)籽輪T
接下來可以進(jìn)行消息檢查者的測試了。但因?yàn)橄z查者的工作牽到其它兩個角色,所以測試之前必須要先簡單模擬出這兩個龍?zhí)捉巧合⑻幚碚吆拖⒈O(jiān)聽者。
消息處理者(MessageHandler )的作用顧名思義不用多作解釋,它在 GUI 框架當(dāng)中的作用十分重要,實(shí)現(xiàn)起來也最復(fù)雜,但在這本文中它不是主角,所以可以先隨便拉個路人甲來跑跑龍?zhí)祝啡思组L得像這個樣子:
1: // 路人甲表演的消息處理者
2: typedef void (*MessageHandler)( const Message& message );
消息監(jiān)聽者(MessageListener )保存有消息檢查者與消息處理者的映射,并提供接口操作這些映射,當(dāng)監(jiān)聽到消息的時候,消息監(jiān)聽者調(diào)用映射中的檢查者進(jìn)行檢查,如果通過檢查則調(diào)用消息處理者來進(jìn)行處理。消息監(jiān)聽者干的都是添加/刪除/查找這樣的體力活,看似實(shí)現(xiàn)起來用不著大腦,可是當(dāng)涉及到真正的消息處理時情況會變得復(fù)雜,會遇到消息重入之類的問題。所以真正的實(shí)現(xiàn)留待日后再說,這里也先給出一個簡單的模擬定義含混過去:
1: // 消息監(jiān)聽者
2: class MessageListener
3: {
4: typedef vector<MessageHandler> _HandlerVector;
5: typedef pair<MessageChecker,_HandlerVector> _HandlerPair;
6: typedef vector<_HandlerPair> _HandlerMap;
7: typedef _HandlerVector::iterator _HandlerVectorIter;
8: typedef _HandlerMap::iterator _HandlerMapIter;
9:
10: public:
11: virtual ~MessageListener(){}
12:
13: // 消息從這里來了
14: virtual void onMessage( const Message& message );
15:
16: public:
17: // 操作映射的接口
18: bool addHandler( const MessageChecker& checker,const MessageHandler& handler );
19: bool removeHandler( const MessageChecker& checker,const MessageHandler& handler );
20: bool clearHandlers( const MessageChecker& checker );
21:
22: protected:
23: // 消息檢查者與消息處理者的映射
24: _HandlerMap m_map;
25: };
其中調(diào)用消息檢查者的是關(guān)鍵函數(shù)是 MessageListener::onMessage( const Message& mesage ) ,實(shí)現(xiàn)很簡單,查找出消息處理者列表然后逐個調(diào)用其中處理者:
1: // 調(diào)用檢查者檢查,通過則調(diào)用處理者處理
2: void MessageListener::onMessage( const Message& message )
3: {
4: for ( _HandlerMapIter it = m_map.begin(); it!=m_map.end(); ++it )
5: {
6: if ( (*it).first.isOk(message) )
7: {
8: _HandlerVector* handers = &(*it).second;
9: if ( handers && !handers->empty() )
10: {
11: for ( _HandlerVectorIter it=handers->begin(); it!=handers->end(); ++it )
12: {
13: (*it)( message );
14: }
15: }
16: }
17: }
18: }
2.6 進(jìn)行測試
龍?zhí)锥家丫臀唬瑢?dǎo)演喊:"action!",現(xiàn)在可以寫點(diǎn)測試代碼測試一下了:
1: void handleCreated( const Message& message )
2: {
3: cout<<"::handleCreated";
4: cout<<"\n"<<endl;
5: }
6:
7: void handleCommand( const Message& message )
8: {
9: cout<<"::handleCommand\t";
10: cout<<"id:"<<LOWORD(message.wparam);
11: cout<<"\t";
12: cout<<"code:"<<HIWORD(message.wparam);
13: cout<<"\n"<<endl;
14: }
15:
16: void handleClicked( const Message& message )
17: {
18: cout<<"::handleClicked\t";
19: cout<<"id:"<<LOWORD(message.wparam);
20: cout<<"\n"<<endl;
21: }
22:
23: void handleRangeCommand( const Message& message )
24: {
25: cout<<"::handleRangeCommand\t";
26: cout<<"id:"<<LOWORD(message.wparam);
27: cout<<"\t";
28: cout<<"code:"<<HIWORD(message.wparam);
29: cout<<"\n"<<endl;
30: }
31:
32: void handleString( const Message& message )
33: {
34: cout<<"::handleString\t";
35: cout<<"string:"<<(const char*)message.lparam;
36: cout<<"\n"<<endl;
37: }
38:
39:
40: #define ID_BUTTON_1 1
41: #define ID_BUTTON_2 2
42: #define ID_BUTTON_3 3
43: #define ID_BUTTON_4 4
44:
45: int main( int argc,char** argv )
46: {
47: MessageListener listener;
48: listener.addHandler( MessageChecker(WM_CREATE),&handleCreated );
49: listener.addHandler( CommandChecker(ID_BUTTON_1,BN_CLICKED),&handleCommand );
50: listener.addHandler( RangeIdCommandChecker(ID_BUTTON_1,ID_BUTTON_3,BN_CLICKED),&handleRangeCommand );
51: listener.addHandler( StringMessageChecker( "I love this game" ),&handleString );
52:
53: Message message( WM_CREATE );
54: listener.onMessage( message );
55:
56: message.id = WM_COMMAND;
57: message.wparam = MAKEWPARAM( ID_BUTTON_2,BN_CLICKED );
58: listener.onMessage( message );
59:
60: message.id = WM_COMMAND;
61: message.wparam = MAKEWPARAM( ID_BUTTON_1,BN_CLICKED );
62: listener.onMessage( message );
63:
64: message.id = WM_COMMAND;
65: message.wparam = MAKEWPARAM( ID_BUTTON_3,BN_CLICKED );
66: listener.onMessage( message );
67:
68: const char* string = "I love this game";
69: message.id = WM_SETTEXT;
70: message.lparam = (LPARAM)string;
71: listener.onMessage( message );
72:
73: return 0;
74: }
3 收場的話
這第一錘終于砸完了,石頭一裂為二,胸口完好無損。其實(shí)砸的時候心想,這一錘的分量砸下去不轟動神州也要震驚天府吧。但是回頭看看上面所有的文字,覺得這個東西怎么這么簡單,甚至連模板參數(shù)都沒有用到一個,更沒有談到效率,優(yōu)化什么的,肯定是不足以誘惑技術(shù)流的 cpper 們的。
想起自己曾經(jīng)寫過的幾個消息框架,可以算是把 C++ 的編譯期技術(shù)發(fā)揮得淋漓盡致了,但是出來的東西卻并不理想,后來慢慢領(lǐng)悟到一個道理:高尖的技術(shù)雖然炫酷,并不是處處都合適用。我的版本的消息檢查者就止于這個程度了,肯定有比這個更好的實(shí)現(xiàn),希望走過路過的高手們不要吝嗇自己的好想法,提出來與廣大醬油眾分享。
消息檢查總算寫完了。沒選上好季節(jié),電腦前坐了大半天手腳都冰涼的。上床睡覺去了,養(yǎng)足精神希望能看到新一輪的神仙打架。文章涉及的所有代碼項(xiàng)目下載:MessageChecker_200911251055.rar
Feedback
貌似沙發(fā)又得被我占領(lǐng)…… 作為一個對oo不屑一顧的c(pp)er,看到這樣的代碼相當(dāng)不適……
比如MessageChecker和它的子類…… 不適啊……
1. 切割與多態(tài) 從msvc和gcc的實(shí)現(xiàn)來看,虛指針是切割不掉的,它在父類中。 虛表就更不存在切割一說…… 不能多態(tài)的原因不是因?yàn)榍懈睿且驗(yàn)?—— 沒有使用引用或指針類型進(jìn)行調(diào)用…… 所以啊,對這種情況 …… 可以惡心點(diǎn)…… 按值保存(會被切割),同時對每個值也保存一個對應(yīng)的指針,指向該值。 通過指針而非值進(jìn)行調(diào)用,誘使編譯器使用虛指針。
為什么感到惡心呢…… 是因?yàn)檫@樣可以多態(tài)調(diào)用,但不能保證子類的不變式…… 調(diào)用的是子類的函數(shù),使用的是父類的數(shù)據(jù)(被切割了)……
2. class vs struct 當(dāng)然,對這個問題蠻適合的。 因?yàn)闃侵鞯姆桨冈谧宇愔型瑯硬荒苋碌臄?shù)據(jù),否則一樣會被切割。 然后,調(diào)用那個函數(shù)指針時,就會訪問到無效內(nèi)容。
為什么感到不適呢…… 上面說了,只要是按值存儲,子類就不可能添加數(shù)據(jù)成員 —— 否則就會被切割。 也就是說,無論怎樣繼承,數(shù)據(jù)成員就那么幾個…… 這里需要的其實(shí)不是class, 而是struct …… 強(qiáng)行使用class, 會使得需要定制的時候,就需要定義一個類 —— 其實(shí)僅僅需要定制一個函數(shù)就可以了。
struct message_checker {
id; wp; lp; int (*is_ok) (const message_checker&,const message& );
};
message_checker ButtonClickingChecker(WORD id); message_checker xxxChecker( ... );
所以,怎么看怎么別扭…… 當(dāng)然,這是口味問題…… 不值得爭個你死我活……
3. 其他 有些不需要id,wp,lp的checker該怎么辦呢…… 需要比id,wp,lp更多參數(shù)的checker又該怎么辦呢……
for (_HandlerMapIter it = m_map.begin(); it!=m_map.end(); ++it) 這個效率是很低的…… _HandlerMapIter的命名也是不符合c/c++規(guī)范的…… 都被Gof帶壞了……
@OwnWaterloo 【強(qiáng)行使用class, 會使得需要定制的時候,就需要定義一個類 —— 其實(shí)僅僅需要定制一個函數(shù)就可以了。】 這樣的設(shè)計也是跟框架的整體設(shè)計有關(guān)系,以后寫了消息映射者你再看看。倒是沒考慮過使用函數(shù)來實(shí)現(xiàn)消息檢查者,我更喜歡它有對象的感覺一點(diǎn),OOP語言嘛,而且類是肯定比函數(shù)有大得多的靈活性的。
【有些不需要id,wp,lp的checker該怎么辦呢……需要比id,wp,lp更多參數(shù)的checker又該怎么辦呢……】 之所以不需要再需要新的成員數(shù)據(jù),是因?yàn)橄⒈旧砭椭挥心菐讉€數(shù)據(jù)。如果需要檢查更復(fù)雜的情況,比如說字符串,這樣的模式也是可以勝任的,以前實(shí)現(xiàn)過。在 MessageChecker 當(dāng)中加一個多態(tài)的 Data 成員,它的多態(tài)復(fù)雜度要比 MessageChecker 本身在堆上分配的小得多,目前沒遇到id,wparam,lparam 三個參數(shù)解決不了問題的情況,暫時不會增加這個 Data 成員。
【從msvc和gcc的實(shí)現(xiàn)來看,虛指針是切割不掉的,它在父類中。虛表就更不存在切割一說……】 這樣走旁門左道是因?yàn)橐郧按_實(shí)遇到過虛函數(shù)失效的問題,應(yīng)該不會真是我忘了用指針調(diào)用了吧?更可能是編譯器bug,因?yàn)橹羔樥{(diào)用虛函數(shù)從小就記得。VC71對于C++標(biāo)準(zhǔn)的支持不是很理想,遇到過很多編譯器報“cl.exe 內(nèi)部錯誤”,特別是涉及模板的時候。
【是因?yàn)檫@樣可以多態(tài)調(diào)用,但不能保證子類的不變式……調(diào)用的是子類的函數(shù),使用的是父類的數(shù)據(jù)(被切割了)……】 什么不變式要變式哦,聽起來好討厭,能抓貓的就是牛老鼠。寫出來的程序編譯通過鏈接成功,客戶能運(yùn)行它的時候覺得心里爽就行了,所有的規(guī)則應(yīng)該是為這個最終的目的服務(wù)的,而不應(yīng)該為規(guī)則而規(guī)則。
【“for (_HandlerMapIter it = m_map.begin(); it!=m_map.end(); ++it)這個效率是很低的……HandlerMapIter的命名也是不符合c/c++規(guī)范的…… 都被Gof帶壞了……】 我倒是并不覺得它的效率很低,因?yàn)橹皇鞘啻蔚难h(huán),不過也希望看看你有更有效率的方法。命令風(fēng)格是被翻炒了千億次的問題了,使程序員從編碼到設(shè)計都能保持最大的個性,我覺得正是C++的一大特色。就算有人腰上圍一圈炸彈手執(zhí)打火機(jī)來逼我改風(fēng)格,我依然視死如歸地覺得自己的命名方式美妙絕倫無以倫比的,堅(jiān)持風(fēng)格得有血可流有頭可斷發(fā)型不能亂的勇氣啊。所以你就將就著看了。
“規(guī)則,風(fēng)格,效率”,OwnWaterloo兄弟啊,你就是典型的傳說中的學(xué)院派 cpper 。很高興又來占我沙發(fā),同時很是抱歉,這篇博文可能讓你失望了。但我會再接再勵,希望后續(xù)的文章能引起你的興趣,不負(fù)你兩占沙發(fā)的厚望。
# re: GUI框架:消息檢查者[未登錄] 回復(fù) 更多評論
2009-11-22 12:57 by
下午還有課!雖然是星期天!!
唉。
這么好的文章,竟然只能晚上才能拜讀,遺憾啊!
多謝cexer分享,期待下文...
@Loaden 謝謝老鄧!也希望你的框架再進(jìn)化!
對GUI我早就煩了。。。 歸根結(jié)底,GUI是個適合于自動生成的玩意兒。 實(shí)在不行,還是學(xué)Qt吧,MOC解決所有問題。。。
@cexer 剛醒…… 一邊吃飯一邊先把不涉及風(fēng)格的東西解釋一下……
不說不變式了。我也覺得這詞太學(xué)究,所以在后面補(bǔ)了一句: 【調(diào)用的是子類的函數(shù),使用的是父類的數(shù)據(jù)(被切割了)……】
而且,我說錯了…… 編譯器會在復(fù)制(以及切割)的同時修正虛指針 —— 忘了…… 要memcpy( &b, &d, sizeof(b) ); 才行……
關(guān)于【for (_HandlerMapIter it = m_map.begin(); it!=m_map.end(); ++it)】 我指的不是for each。而是遍歷。 不明白為什么只需要遍歷,卻用了一個map。 然后…… 我再看了看這行代碼之上的代碼,這是一個vector…… 我又看錯了…… 代碼看得不仔細(xì)……
關(guān)于coding-style。其實(shí)我是最不講究這個的了…… 但有些東西不屬于coding-style,而是屬于底線 —— 使用c/c++語言必須遵守的 —— 不能使用語言保留的標(biāo)識符。 語言保留的標(biāo)識符還分了幾種, 統(tǒng)一起來就是以下劃線開始的程序員全都不能使用。 這在很多書里面都有說,又在很多書(比如Gof,不知道算不算經(jīng)典)中犯錯。
@cexer 說說我對gui框架的需求吧…… 以用戶的身份…… 用戶的需求總得聽聽吧……
從這里說起: 【目前沒遇到id,wparam,lparam 三個參數(shù)解決不了問題的情況】 我編寫gui的經(jīng)驗(yàn)不多。所以對"這3個參數(shù)是否能解決所有問題"是一點(diǎn)概念都沒有。
不過我想了一個例子,比如我要在xxx-yyy時間段內(nèi),檢查這個消息,返回真,這個時間段外,就返回假。 這個checker就做不到了。 肯定要放到其他地方做,比如hanlder中。
對,這就是我對"框架"感到反感的地方之一 —— 它"限制"你做事的方法。 它會將可能本來是一件完整的工作,拆散,然后分散到框架的一些地方去。
為了完成這個工作,你要按框架所拆分的那樣 —— 需要順著框架的思路,而非我自己的思路 ——只需要id,wp,lp就能check的,放到checker中。其他即使也是check,也得放到handler中。 如果框架拆分得恰當(dāng),就很好,比如 CommandChecker等。 如果框架拆分得不恰當(dāng),就很糟…… 所以我對mfc一點(diǎn)好感都沒有。
作為一個了解且不排斥win32api的用戶(當(dāng)然,gui框架大多著眼的都不是這種用戶囧……),我需要的僅僅是彌補(bǔ)一下WndProc,使得它可以找到this —— 這個工作幾乎是必須做的,即使是用C編寫gui的家伙。 然后,就可以從hwnd,msg,wp,lp開始干活了 —— 不需要再學(xué)任何關(guān)于框架的知識,比如要將check放在哪,hanlder放在哪。 我希望框架提供一個,在window上add(掛)多個listener的機(jī)制就差不多了…… 如何分配checker和handler還有cracker的工作,還是將它們?nèi)嘣谝黄穑脩艨梢宰孕袥Q定。
所以我對lambda表達(dá)式特別渴望…… 比如上面的代碼,對應(yīng)的偽代碼可能是這樣:
window w; w.add_listener( void (const msg& m) { if (m.msg==WM_CREATE) cout<<"create\n"<<endl; } );
然后,在每天編程閑暇的時候,發(fā)現(xiàn)庫(而非框架)里面有command_check,我就可以這樣寫代碼了: w.add_listener( void (const msg&m) { if (is_command(m,id) cout<<command_crack(m).id<<endl; } );
如果我沒發(fā)現(xiàn)command_check,我也可以開始我的工作,只是代碼更繁瑣一些。
這就是庫和框架的一個很大的區(qū)別。 你可以使用熟悉的方式工作,然后慢慢熟悉這個庫,慢慢用它干善自己的工作,并提高效率。 而框架,不對它熟悉到一定程度,就沒法工作……
作為程序員…… 我也了解用戶的需求是很惡心的…… 就像你有一篇blog里寫的那樣…… 所以,推己及人…… 對我提出的這個惡心的需求,聽過之后就當(dāng)是廢話好了~_~
@OwnWaterloo 【剛醒…… 一邊吃飯一邊先把不涉及風(fēng)格的東西解釋一下……】 你們那里沒有出太陽?這么大好時光不要拿來睡覺浪費(fèi)了。回復(fù)完這個,我就出去曬太陽了。
【調(diào)用的是子類的函數(shù),使用的是父類的數(shù)據(jù)(被切割了)……】 函數(shù)指針和虛函數(shù)的唯一區(qū)別就是函數(shù)指針少個讀表的動作。所以如果說這樣破壞了“不變式”,那么虛函數(shù)也是一樣的。使用子類函數(shù)讀父類數(shù)據(jù),這種手法是經(jīng)常被用到各種模式中的,這里也不存在切割問題,因?yàn)橐呀?jīng)沒有什么可以切割的。
【語言保留的標(biāo)識符還分了幾種, 統(tǒng)一起來就是以下劃線開始的程序員全都不能使用。】 這只專家們一個很學(xué)究氣的建議,跟“謹(jǐn)慎使用內(nèi)聯(lián)函數(shù)”一樣的警示級別。因?yàn)闃?biāo)準(zhǔn)庫的作者確實(shí)使用了一些下劃線的變量,boost 也有很多下劃線開始的東西,像占位符 _1,_2,..._n。為了避免名字沖突確實(shí)少用為妙,但用了也不應(yīng)該看作錯誤。
@cexer 好像沒有,我是宅男……
boost有自己的苦衷。它需要一個簡短的東西來做占位符。 這樣寫就太繁瑣了: bind( f, first, second , ... ) (); 而且first,second估計很容易和程序員沖突。 1、2又不是合法的標(biāo)識符。 所以干脆搞成_1, _2,至少不會和程序員沖突了。 bind( f, _1, _2 ... )( ... ); 直觀
而_HandlerMapIter改為HandlerMapIter或者HandlerMapIter_并不會影響什么,而且也符合與C/C++定下的契約。
boost將_1,_2改為1_,2_是不合法的,不能以數(shù)字開頭。 改為其他,不太簡潔……
也不能算完全的學(xué)究。它們不會永遠(yuǎn)被保留。 C99已經(jīng)開始動用這些保留的標(biāo)識符了。 _LongLong, _Complex,_Bool等。 C++也會跟進(jìn)。
將_HandlerMapIter改為HandlerMapIter或者HandlerMapIter_真的沒有壞處。 本來就是一個private,庫用戶不可見的名字。 屬于Handler,也不會和其他重名。 還避免了(可能微乎其微)與_HandlerMapIter宏沖突。
@OwnWaterloo 【boost有自己的苦衷。它需要一個簡短的東西來做占位符。】 你誤會我的意思了。我是同意你的,我的意思是確實(shí)應(yīng)該避免使用,因?yàn)橄?boost,stl 之類的庫很多地使用了這樣的下劃線。
【將_HandlerMapIter改為HandlerMapIter或者HandlerMapIter_真的沒有壞處。 本來就是一個private,庫用戶不可見的名字。屬于Handler,也不會和其他重名。還避免了(可能微乎其微)與_HandlerMapIter宏沖突。】 嗯,明白你的意思。其實(shí)這是我的習(xí)慣,外部不可見的都加個下劃線在前頭,沒考慮到與標(biāo)準(zhǔn)庫發(fā)生沖突的情況,你想的更細(xì)致一點(diǎn)。
【不過我想了一個例子,比如我要在xxx-yyy時間段內(nèi),檢查這個消息,返回真,這個時間段外,就返回假。 這個checker就做不到了。 肯定要放到其他地方做,比如hanlder中。】 是可以的啊,可以像下面這樣實(shí)現(xiàn)。 class TimeMessageChecker:public MessageChecker { public: TimeMessageChecker( timeStart,timeTo ) :MessageChecker( 0,timeStart,timeTo,&TimeMessageChecker:virtualIsOk ) {} protected: static bool virtualIsOk( const MessageChecker* pthis,const Message& ) { timeNow = GetCurrentTime(); timeStart = pthis->wparam; timeTo = pthis->lparam; return timeNow>=timeStart && timeNow<=timeTo; } }
可能你的意思是遇到消息參數(shù)裝不下檢查時需要的信息的情況應(yīng)該怎么辦,這里舉例說明一下怎么實(shí)現(xiàn)。比如說要檢查是否某個字符串,這樣的實(shí)現(xiàn)可以裝下任何需要的信息。 先定義一個多態(tài)的數(shù)據(jù)類型,為了效率這里可以使用引用計數(shù)之類的東西 class MessageData { public: virtual ~MessageData() {} virtual void release() { delete this; } virtual MessageData* clone() const = 0; };
MessageChecker 當(dāng)中有這個成員 class MessageChecker { //...... public: MessageData* data; }
修改拷貝構(gòu)造函數(shù)和析構(gòu)函數(shù) MessageChecker::MessageChecker( const MessageChecker& other ) { if ( data ) { data->release(); data = NULL; } if ( other.data ) { data = other.data->clone(); } }
MessageChecker::~MessageChecker() { if ( data ) { data->release(); data = NULL; } }
定義一個裝字符串的多態(tài)數(shù)據(jù)類 class StringMessageData:public MessageData { public: StringMessageData( const String& str_ ) :string( str_ ) {} virtual MessageData* clone() const { return new StringMessageData(string); } public: String string; };
利用數(shù)據(jù)成員 data 所裝的信息可以檢查字符串的消息檢查者 class StringMessageChecker:public MessageChecker { public: StringMessageChecker( const String& string ) :MessageChecker( 0,0,0,&StringMessageChecker::virutalIsOk ) { data = new StringMessageData( string ); } public: static bool virtualIsOk( const MessageChecker* pthis,const Message& message ) { StringMessageData* data = (StringMessageData*)pthis->data; if ( !data ) { return false; } std::string stirngToCheck = (const Char*)( message.wparam ); return stirngToCheck == data->string; } };
【對,這就是我對"框架"感到反感的地方之一 —— 它"限制"你做事的方法。不對它熟悉到一定程度,就沒法工作…… 作為程序員……我也了解用戶的需求是很惡心的…… 就像你有一篇blog里寫的那樣……所以,推己及人……對我提出的這個惡心的需求,聽過之后就當(dāng)是廢話好了~_~】 說實(shí)話我一直對“框架”和“類庫”的概念分不大清楚,我想“框架”從名字上來說多了一個“可以擴(kuò)展,可以填充”的意思,沒有你說的“限制你做事的方法”這種感覺。還有你提到了 GUI 框架用戶的需求。要說明一下的是,實(shí)現(xiàn)的這個消息檢查者只是在框架內(nèi)工作的,方便 GUI 框架的開發(fā)者和擴(kuò)充者使用,GUI 框架的使用者不會接觸到這個東西。最終的用戶只需要使用是像這個樣子: window.onCreated += messageHandler( &::_handleCreated ); window.onCreated += messageHandler ( this,&Window::_handleCreated );
cppblg 這爛程序,吃空格太嚴(yán)重了。
@cexer 確實(shí)…… cppblog的評論做得不咋嘀…… 所以代碼我也沒仔細(xì)看…… 意思差不多明白了。 WndProc可以用這些參數(shù)表達(dá)任意多的參數(shù),所以我們也可以…… 對不需要多余參數(shù)的,可以直接使用這幾個。 對需要的,再引入多態(tài)與自由存儲的開銷,并將其包裹在一個值語意的類型中。 是這樣嗎?
這思路不錯。 預(yù)留幾個常用的參數(shù),免得需要參數(shù)就要動用動態(tài)內(nèi)存……
【 說實(shí)話我一直對“框架”和“類庫”的概念分不大清楚,我想“框架”從名字上來說多了一個“可以擴(kuò)展,可以填充”的意思,沒有你說的“限制你做事的方法”這種感覺。 】 哎…… 因果弄反了…… 為什么需要【擴(kuò)展】框架? —— 就是因?yàn)榭蚣芟拗屏俗鍪碌姆绞剑粩U(kuò)展就沒法做自己需要但框架又不提供的功能了。
說得好聽點(diǎn),叫擴(kuò)展了框架;說得不好聽,叫順了框架的意;再難聽點(diǎn),就是被框架qj了……
以你這篇文章里提到框架來說吧: 1個listener 有 1個m_map 1個m_map 有若干個HanlderPair 1個HandlerPair 包含一個 Checker和HandlerVector 1個HandlerVector 包含若干個Handler
這就是一種限制。MessageChecker也算。
設(shè):從源頭WndProc到實(shí)現(xiàn)某個功能所需要做的全部事情為S。 【必須按這個框架制定的規(guī)則,將S拆分為S1、S2等等,然后安排到框架中的預(yù)定位置。】 比如將事情分為監(jiān)聽、檢查、映射、處理等工作。
這就是我說的限制的意思。【框架規(guī)定了編程的模型】。
假設(shè),有一項(xiàng)需求框架沒有提供,比如Checker需要更多的參數(shù)。 如果還想繼續(xù)使用這個框架,就必須設(shè)計一個MessageData。 假設(shè)MessageData用戶而非框架提供的,是為【擴(kuò)展】了框架。 假設(shè)MessageData是框架自身提供的,但如果它又有另一個需求沒有滿足呢? 它必須提供一些【可擴(kuò)展點(diǎn)】讓用戶【大部分按框架的思路編程】,并在需要的時候,擴(kuò)展一下框架。否則用戶就會放棄這個框架了。
如果框架的規(guī)定在大部分時候合理,框架就是合理的。 這個尺度很難把握……
總之,用框架編程,框架是主體。或者說底層的實(shí)現(xiàn)不用用戶操心,只需要注重業(yè)務(wù)邏輯。 用類庫編程,程序員自己是主體。
【 倒是沒考慮過使用函數(shù)來實(shí)現(xiàn)消息檢查者,我更喜歡它有對象的感覺一點(diǎn),OOP語言嘛,而且類是肯定比函數(shù)有大得多的靈活性的。 】 這個,其實(shí)也是反的…… OO是思想,class只是它的一種實(shí)現(xiàn)方式。也許使用會比較方便。 但靈活性是不如函數(shù)+數(shù)據(jù)的。
1. 比如C++、java、C#都沒有提供多重分派(multi-method)。 所以,很明顯的,o.method(...); 只是method( o , ... );在1個對象下的簡便形式。 如果需要method( o1, o2, ... ) —— 根據(jù)o1和o2的類型來決定做什么事。這3門語言是不提供支持的,也必須用函數(shù)來表達(dá)。 我知道有個xxx模式…… 但3重分派它又解決不了了…… 治標(biāo)不治本。
當(dāng)然,某種支持多分派的語言可以繼續(xù)提供 o1&o2.method( ... ); 的語法……
2. OO將算法埋葬到自己的類里。 如果這個算法很通用,更好的是將它實(shí)現(xiàn)為一個函數(shù),而不是委身到一個class中。 呃,這種情況在gui中可能不多見。
想起一個說明限制的更通俗的例子:std::for_each。 它就算一種微型框架。 它完成了遍歷的步驟,同時限制了遍歷的方法—— 用一元函數(shù)。
使用for_each時 1. 直接存在現(xiàn)有的一元函數(shù): vector<void*> m; // memory blocks for_each(m.begin(), m.end(), free );
2. 可以通過for_each配套提供的一些設(shè)施完成工作: vector<shape*> s; for_each(s.begin(), s.end(), bind2nd(mem_fun(&s::draw),canvas) ); for_each(s.begin(), s.end(), mem_fun(&s::reset) );
3. 擴(kuò)展for_each —— 添加更多functor vector<elem> e; for_each(e.begin(), e.end(), boost::bind( &e::f, ... ) );
但怎么做,都只是個binder而已。還有一些人做出一些帶有簡單邏輯的functor,然后繼續(xù)使用std::for_each,語法丑陋得,我都不知道怎么寫…… 所以這里就不列了…… toplanguage上可以找到不少……
4. 當(dāng)這些都失效時…… 可以說,for_each這個框架在大多數(shù)時候都是雞肋。 讓人不得不繞過它的限制(傳遞一元函數(shù)),直接寫for循環(huán)。
而boost.lambda和C++0x的lambda使得for_each變得實(shí)用了不少。所以框架是否實(shí)用,很難把握。 說for_each是框架嘛…… 主要是因?yàn)樗南拗啤?br>但是它規(guī)模有點(diǎn)小……也可以很輕易的被丟掉。反正是函數(shù)模板,不使用就不會被實(shí)例化。這又有類庫的性質(zhì)。 算是一個不太恰當(dāng)?shù)睦影伞?br>
框架定義了輪廓線,由程序員去填充顏色。 類庫提供調(diào)色板等工具,由程序員去繪制。
另外,傳遞給for_each的一元函數(shù)的調(diào)用語法是: f( *first ); 而不是 ((*first).*f)(); 也是一個自由函數(shù)比成員函數(shù)更普適與靈活的例子。
將成員函數(shù)以自由函數(shù)的語法進(jìn)行調(diào)用: bind(&c::f)( o, ... );
將自由函數(shù)以成員函數(shù)語法調(diào)用…… 好像要定義一個類……
@OwnWaterloo 【框架定義了輪廓線,由程序員去填充顏色。類庫提供調(diào)色板等工具,由程序員去繪制。】 這句話比喻得很好。你把框架理解成一種束縛是覺得需要按照它規(guī)定的方式去使用,但類庫甚至 API 何嘗不是也有自己的規(guī)則。我理解的框架和類庫其基本目的都是一樣的:對繁瑣的細(xì)節(jié)進(jìn)行封裝,提供更為便利的接口。這當(dāng)中肯定會損失靈活性,畢竟魚和熊掌很難兼得。但我覺得框架其實(shí)是類庫的進(jìn)化后的身份,從字面意義上來講,“框架”看起來更有彈性,擴(kuò)展的意思。但實(shí)際上大家對這兩個詞的概念并沒有很明白的分辨,比如說到 MFC,有人說框架有人說類庫大家的感覺都是一樣的。
【這個,其實(shí)也是反的……OO是思想,class只是它的一種實(shí)現(xiàn)方式。也許使用會比較方便。但靈活性是不如函數(shù)+數(shù)據(jù)的。】 我的想法相反。你說類的靈活性不如函數(shù)加數(shù)據(jù),但類難道不正是建立在函數(shù)和數(shù)據(jù)之上的一個超強(qiáng)結(jié)合體?之所以用C之類的 OP 語言實(shí)現(xiàn)模式不如C++這樣的 OO 語言容易,一大原因正是它缺少類的支持。
【message_checker ButtonClickingChecker(WORD id); message_checker xxxChecker( ... );】 這是你舉例說明的用函數(shù)來實(shí)現(xiàn)檢查者。你可以嘗試真的用函數(shù)來實(shí)現(xiàn)消息檢查者,這個 ButtonClickingChecker(WORD id) , xxxChecker( ... ) 函數(shù)內(nèi)部你各自要怎么實(shí)現(xiàn)?它既要包含不同的數(shù)據(jù),又要包含不同的操作,它們返回完全相同的 message_checker 對象,這個 message_checker 又要怎么實(shí)現(xiàn)才能以一已之身完成眾多不同的功能?由于不同參數(shù)列表的函數(shù)實(shí)際上是完全不同的東西,你甚至不能以統(tǒng)一的方式保存它們管理它們。
繼續(xù)攪局:你憑什么認(rèn)為MessageChecker父類的成員變量一定夠用呢?如果夠用的話,就證明你的邏輯已經(jīng)是固定的了,為什么要子類?就一個MessageChecker好了,不用繼承。你矛盾了。
@cexer
你說類的靈活性不如函數(shù)加數(shù)據(jù),但類難道不正是建立在函數(shù)和數(shù)據(jù)之上的一個超強(qiáng)結(jié)合體?
類的靈活性在于,你使用shared_ptr<Base>保存了一個Derived*,然后調(diào)用虛函數(shù)。代價非常低,我經(jīng)常把智能指針放進(jìn)容器,也可以不管誰去釋放,總之會被釋放。而且我這種寫compiler的人對循環(huán)引用十分敏感所以我基本不會犯這種錯誤……
@cexer
【這是你舉例說明的用函數(shù)來實(shí)現(xiàn)檢查者。你可以嘗試真的用函數(shù)來實(shí)現(xiàn)消息檢查者,這個 ButtonClickingChecker(WORD id) , xxxChecker( ... ) 函數(shù)內(nèi)部你各自要怎么實(shí)現(xiàn)?它既要包含不同的數(shù)據(jù),又要包含不同的操作,它們返回完全相同的 message_checker 對象,這個 message_checker 又要怎么實(shí)現(xiàn)才能以一已之身完成眾多不同的功能?由于不同參數(shù)列表的函數(shù)實(shí)際上是完全不同的東西,你甚至不能以統(tǒng)一的方式保存它們管理它們。】
TR1有functor給你用,解決了這些問題。無論是函數(shù)指針,成員函數(shù)指針,有operator()的類,統(tǒng)統(tǒng)都可以放進(jìn)去。所以我覺得你的listener應(yīng)該是
vector<function<bool(Message&)>>,然后處理了返回true,沒處理返回false。function自己可以有自己的成員,你可以把有operator()的對象放進(jìn)去,把成員函數(shù)放進(jìn)去什么的,統(tǒng)統(tǒng)都可以。要堅(jiān)定不移地使用這些東西。
@cexer
而且,千萬不要去考慮標(biāo)準(zhǔn)庫里面什么類的性能如何的問題,你永遠(yuǎn)假定他們是用魔法完成的,不需要任何CPU周期。不然你一行代碼都寫不出來。
@cexer
至于什么是框架什么是類庫,有一個很容易的判斷標(biāo)準(zhǔn):template method就是框架。
@cexer
所以框架和類庫可以互相包含,這也是很常見的。
@陳梓瀚(vczh) 【繼續(xù)攪局:你憑什么認(rèn)為MessageChecker父類的成員變量一定夠用呢?如果夠用的話,就證明你的邏輯已經(jīng)是固定的了,為什么要子類?就一個MessageChecker好了,不用繼承。你矛盾了。】 歡迎攪局!你提出一問題確實(shí)有“以子之矛攻子之盾”的殺傷力,不過幸好我不是既賣矛又賣盾的。關(guān)于數(shù)據(jù)不夠用的情況 OwnWaterloo 的也提出過,你可以倒著往上看給 OwnWaterloo 的回復(fù),在 MessageChecker 當(dāng)中加一個多態(tài)的 Data 成員可以放下所有東西,我還是把這東西更新到博文里去吧免得又有人問。“邏輯”的關(guān)鍵顯然不是數(shù)據(jù),而是那個檢查的動作。就像銀行存了五千萬,不拿出去揮霍也住不上豪宅一樣,成員變量夠用成員函數(shù)不夠用也是白搭,必須得要改寫。要想一個類,不繼承,不改寫,而要滿足不斷出現(xiàn)的需求,這肯定是不能完成的任務(wù)。“數(shù)據(jù)夠用”和“函數(shù)改寫”并矛盾。
【類的靈活性在于,你使用shared_ptr<Base>保存了一個Derived*,然后調(diào)用虛函數(shù)。代價非常低,我經(jīng)常把智能指針放進(jìn)容器,也可以不管誰去釋放,總之會被釋放。而且我這種寫compiler的人對循環(huán)引用十分敏感所以我基本不會犯這種錯誤……】 你是建議我把 MessageChecker 在堆上生成用智能指針管理起來吧。我也覺得這樣確實(shí)可以使用真正強(qiáng)大的虛函數(shù),但這樣要付出每次都在堆上構(gòu)造的效率成本和管理上的復(fù)雜性,而實(shí)際上大多數(shù)情況下這樣的付出是不必的:在 Windows 的消息機(jī)制下,消息檢查大多數(shù)時候需要的數(shù)據(jù)是很少的,用 WPARAM 和 LPARAM 就可以裝下,但確實(shí)有需要在堆上保存數(shù)據(jù)的情況,所以我在 MessgaeChecker 增加了一個多態(tài)的 Data 成員來保存,它只有極少時候的才會從堆上生成,這樣盡量避免了堆上生成的復(fù)雜性和效率損失,而完成的功能又絲毫不減。
@cexer
使用tr1::function<bool(Message&)>,毫無管理復(fù)雜性,幾乎沒有效率成本。
@陳梓瀚(vczh)
但是實(shí)際上windows的消息很亂,事件跟消息并不是一一對上號的。因此MessageChecker之類的東西最終用戶是看不到的,你要根據(jù)每一個控件的具體情況,重新整理出一系列事件,才能讓用的時候真正爽快起來。這里沒有技術(shù)問題。
@陳梓瀚(vczh) 【要堅(jiān)定不移地使用這些東西。 使用tr1::function<bool(Message&)>,毫無管理復(fù)雜性,幾乎沒有效率成本。】 把 MessageChecker::isOk(const Message&) 的實(shí)現(xiàn)放到 bool MessageChecker::operator()(const Message&) 中去,函數(shù)體MessageChecker 就變成了和 function 類似的東西,但這樣又有什么本質(zhì)區(qū)別呢。tr1::function 的功能雖然強(qiáng)大,用它去實(shí)現(xiàn)消息檢查者,該寫的邏輯還得寫,省不了功夫,擴(kuò)展性也是個問題。
【至于什么是框架什么是類庫,有一個很容易的判斷標(biāo)準(zhǔn):template method就是框架。】 謝謝,很多書對這個也言之不詳,只是叫著爽就行了,所以我自己也就不大明白。我從此以后照你這個標(biāo)準(zhǔn)去評判好了。
【而且,千萬不要去考慮標(biāo)準(zhǔn)庫里面什么類的性能如何的問題,你永遠(yuǎn)假定他們是用魔法完成的,不需要任何CPU周期。不然你一行代碼都寫不出來。】 我是從不考慮那些的:標(biāo)準(zhǔn)庫的效率再差,也不會到我放棄使用它們的地步的。另外以我的水平寫出來的東西絕對比它的慢,哪有資格嫌棄人家。
【但是實(shí)際上windows的消息很亂,事件跟消息并不是一一對上號的。因此MessageChecker之類的東西最終用戶是看不到的,你要根據(jù)每一個控件的具體情況,重新整理出一系列事件,才能讓用的時候真正爽快起來。這里沒有技術(shù)問題。】 你說得對,框架用戶是看不到 MessageChecker 的,他們看到的是 onCreated,onClosed,onClicked 之類的東西。
@cexer
function的好處就是你可以使用子類的同時不用管delete啊
@陳梓瀚(vczh) 【function的好處就是你可以使用子類的同時不用管delete啊】 想了下用 tr1::function 來實(shí)現(xiàn)確實(shí)要比手寫類簡單得多,函數(shù)和參數(shù)綁定功能在這里很有用處,有多少參數(shù)都不用搞個多態(tài)的 Data 了,也不用自己去 delete 。確實(shí)很強(qiáng)大。
@cexer 【我的想法相反。你說類的靈活性不如函數(shù)加數(shù)據(jù),但類難道不正是建立在函數(shù)和數(shù)據(jù)之上的一個超強(qiáng)結(jié)合體?之所以用C之類的 OP 語言實(shí)現(xiàn)模式不如C++這樣的 OO 語言容易,一大原因正是它缺少類的支持。】
是的,類是函數(shù)和數(shù)據(jù)的結(jié)合,還加上數(shù)據(jù)隱藏。 超強(qiáng)到談不上…… 我也說了的,對單個object,這3門語言的OO實(shí)現(xiàn)確實(shí)是方便。
靈活與范化的東西,通常比較難用和不方便。
【 這是你舉例說明的用函數(shù)來實(shí)現(xiàn)檢查者。你可以嘗試真的用函數(shù)來實(shí)現(xiàn)消息檢查者,這個 ButtonClickingChecker(WORD id) , xxxChecker( ... ) 函數(shù)內(nèi)部你各自要怎么實(shí)現(xiàn)?它既要包含不同的數(shù)據(jù),又要包含不同的操作,它們返回完全相同的 message_checker 對象,這個 message_checker 又要怎么實(shí)現(xiàn)才能以一已之身完成眾多不同的功能?由于不同參數(shù)列表的函數(shù)實(shí)際上是完全不同的東西,你甚至不能以統(tǒng)一的方式保存它們管理它們。 】 你那個MessageChecker怎么完成以一己之身完成,眾多不同功能的? 同樣可以應(yīng)用到message_checker 上,兩者是相通的,都是靠結(jié)構(gòu)體中嵌的一個函數(shù)指針。
-------- -------- -------- --------
關(guān)于那個id,wp,lp,我覺得還不錯。
Windows確實(shí)需要將系統(tǒng)與用戶之間使用【一條統(tǒng)一的渠道【來通信。 這條渠道演化到【極端】就是這種形式: void* (*)(void* all_parameter);
但如果所有消息都需要使用動態(tài)內(nèi)存,就有點(diǎn)浪費(fèi)。 添加一些其他比較常用的值參數(shù) vs 和一個值參數(shù)都不用,就是一種折衷。 完全不用,肯定每次都需要動態(tài)內(nèi)存分配。 值參數(shù)多了,又可能在一些情況用不上。
所以,Windows最后選擇了 LRESULT (CALLBACK*)(HWND, UINT, WPARAM, LPARAM); 參數(shù)少,直接傳遞,參數(shù)多,將某個參數(shù)理解為指針…… (此處純猜測……)
所以,迎合WndProc的設(shè)計還不錯。
-------- -------- -------- --------
其實(shí)我是被這段語法吸引的: window.onCreated += messageHandler( &::_handleCreated ); window.onCreated += messageHandler ( this,&Window::_handleCreated );
很像boost.signal。
而且真正吸引人的是樓主說它是廉價工人~_~
boost.function如果不用支持bind,可能可以不動用動態(tài)存儲。 要支持bind…… 而且不使用動態(tài)存儲…… 好像不行…… boost.signal肯定是要動用動態(tài)存儲的。
等著樓主這部分的實(shí)現(xiàn)了~_~
【你那個MessageChecker怎么完成以一己之身完成,眾多不同功能的? 同樣可以應(yīng)用到message_checker 上,兩者是相通的,都是靠結(jié)構(gòu)體中嵌的一個函數(shù)指針。】 我的意思是函數(shù)本身不容易實(shí)現(xiàn),只用函數(shù)主要有數(shù)據(jù)保存的問題。加上 function 來實(shí)現(xiàn)就容易了,直接綁定一些檢查函數(shù)和檢查的數(shù)據(jù): typedef boost::function<bool (const Message&)> MessageChecker
bool checkCommand( const Message& message,WORD id,WORD code ); MessageChecker checkYesClicked = boost::bind( &checkCommand,_1,IDYES,BN_CLICKED );
bool checkMessage( const Message& message,UINT messageId, ); MessageChecker checkCreated = boost::bind( &checkMessage,_1,WM_CREATE );
【其實(shí)我是被這段語法吸引的: window.onCreated += messageHandler( &::_handleCreated ); window.onCreated += messageHandler ( this,&Window::_handleCreated ); 很像boost.signal。 而且真正吸引人的是樓主說它是廉價工人~_~】 += 之前的和之后的是兩個不同的東西,onCreated 是幫助消息映射的一個東西,其實(shí)就是一個轉(zhuǎn)發(fā)調(diào)用,所以成本很低。messageHandler() 是消息處理者和消息分解者共同組成的東西。在這里 ::_handleCreated 和 Window::_handleCreated 參數(shù)列表可以是不同的,這里和 boost.signal 不大一樣,因?yàn)橐粋€ signal 只能對應(yīng)一種 signautre 的 functor 的。
【boost.function如果不用支持bind,可能可以不動用動態(tài)存儲。 要支持bind…… 而且不使用動態(tài)存儲…… 好像不行…… boost.signal肯定是要動用動態(tài)存儲的。】 嗯。主要是管理起來很容易了,任何的參數(shù)直接綁定進(jìn)去就行了,不用自己弄個堆對象來保存,然后還要記得刪除。
@cexer
所以說嘛,用標(biāo)準(zhǔn)庫的時候要假設(shè)他們是0CPU消耗,這樣你就不會想太多多余的事情了。
GUI 框架固然重要, 但漂亮的控件,和所見即所得的GUI設(shè)計器更加重要
Mac OS, IPhone, Andriod都是如此
@lch 一個是手段,一個是目的,何來重要和更重要之說。
我的意思是原生的框架已經(jīng)夠用的情況下,控件的成熟度顯得更重要。
高手討論吧,
新手路過,學(xué)習(xí)中...
以后水平高了再和你們討論...
又見 OwnWaterloo 兄,
還認(rèn)識了cexer,陳梓瀚(vczh) 高手...
@lch 這個我同意。不過到后來的豐富控件,已經(jīng)基本上是體力活了。
我想問個問題,假如消息接受者在收到消失的時候要把消息發(fā)送者刪除怎么辦
# re: GUI框架:消息檢查者[未登錄] 回復(fù) 更多評論
2011-05-22 21:35 by
關(guān)于GUI的文章停了嘛,我在恭候呢。
|