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