青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

cexer

cexer
posts - 12, comments - 334, trackbacks - 0, articles - 0
  C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

GUI框架:談談框架,寫寫代碼

Posted on 2009-11-15 18:09 cexer 閱讀(12377) 評論(176)  編輯 收藏 引用 所屬分類: GUI 、pattern

1 開篇廢話

  我喜歡用C++寫 GUI 框架,因為那種成就感是實實在在地能看到的。從畢業到現在寫了好多個了,都是實驗性質的。什么拳腳飛刀毒暗器,激光核能反物質,不論是旁門左道的陰暗伎倆,還是名門正派的高明手段,只要是 C++ 里有的技術都試過了。這當中接觸過很多底層或是高級的技術,像編譯時類型檢測,運行時代碼修改等等,按實現的不同 GUI 涉及的東西是沒有邊際的。從最開始模仿 MFC,ATL 那樣的實現學到很多東西,然后開始看一些開源的著名的 GUI 框架,像 MFC,WTL,SmartWin++,win32gui,jlib2 ,VCF 獲得很多啟發,到現在似乎有一種已看盡天下 GUI 的感覺。在學習別人的框架和自己的實現過程中,真真實實地感覺自己成長了不少,也有很多感悟。

  寫到這,我作為輪子制造愛好者,在這里向那些喊著"不要重復制造輪子的"批評家們承認錯誤。在有那么多好的輪子的情況下,我不值得浪費地球資源,浪費時間精力來自己手工重復打造。但是不值得歸不值得,在值得和喜歡之間我還是選擇后者。并且人生在世,什么才是值得?我覺得不是拯救人類,為世界和平做貢獻,也不是努力奮斗,為地球人民謀福利,而是簡單地做自己喜歡的事。

  寫過的那些代碼很多都消失在硬盤的海洋里了,但那些挑燈苦想來的感悟還在。在它們也消失之前,我想利用空閑時間把這些覺得有點用處的經驗寫出來,正好這個博客也已經快一年沒更新了。另外也算是對那些發我郵件的朋友的回應。

  我的想法是用一系列日志,按照實現一個 GUI 框架的具體思維遞進過程來闡述實現一個 GUI 框架的具體思維遞進過程。這樣說好像有點遞歸,簡單地解釋就是這一系列日志不是想用《記憶碎片》那樣錯亂的敘述方式來說明一個多有意思的故事,而是盡量簡單自然地記錄一下寫 GUI 框架過程中我的思考。這個遞進過程也就是實現一個 GUI 框架的過程,一系列日志之后,我們將會看到一個長得漂亮眼,極富彈性,能干又節約的 GUI 框架。

  雖然寫的內容都是在 Windows 的 GUI 系統之上,但其原理是觸類旁通的,其它基于消息的 GUI 系統也都大同小異。所用的代碼也都是闡述原理的,自知絕對達不到商業巨作的水準,所以請不要一上來就批判,要知道我只是想分享而已。之所以先這樣說一下,是很害怕那種一上來就"怎么不跨平臺?。?,"怎么都還看得到HWND啊?","怎么不能用成員函數處理消息???"的同志。不喜歡站在高處指著別人的天靈蓋說話的人。要知道車輪也是一步步造出來的,不要一開始就想載著MM在高速路上飆豪車像少年啦飛馳。

  我認為寫技術博客有三種境界,一種是一直在那繪聲繪色地描述自己的魚有多可口多美味,讓讀者只能垂涎興嘆,一種是授人以魚的人,悶頭就擺出來各種生猛海鮮,讓讀者難以消化,還有一種境界是授人以漁,怎么釣魚怎么煮魚都細細地教給讀者。讀博客的人有兩個境界,一種是只吃魚的,一上來就只要代碼,一種是學打魚的,想知其然更想知其所以然。讀博客時我努力做學打魚的類型,自己寫博客時我會努力做到授人以漁的境界。

  另外要說明的是,同樣作為塵世中的一個渺小個體,我大多數時候也是在為生存而奔波勞累著的。除此之外剩余的大多時候,更是要玩游戲,K歌,看電影,陪MM,吃喝玩樂。再剩余用來寫這個的時候不是很多,有可能這一系列日志一夜寫就,也有可能增刪五年披閱十載,孩子都叫爸了還沒完成。所以請大家不要對這個博客抱很大的期待,就當我是路邊街頭的表演,你打醬油經過時偶爾瞟過來一眼就好了。

  要說的廢話終于說完了,下面開始正題。

2 基本概念

  基于消息的 GUI 框架的封裝,一切都圍繞消息展開。復雜的框架設計,明確了需求之后,第一步首先是劃分模塊。所以,要闡述一個設計過程,第一步也應該是先說清最基本的概念和模塊劃分,而不是一上來就用廣義相對論把讀者全部放倒。GUI 框架是干什么的當然是地球人都知道的,但 GUI 框架沒有什么已經劃分的標準概念,我是按照設計的需要來劃分的。如果把 GUI 框架看作一個單位,那么這個單位里最重要的角色有這幾個:

  • 消息發送者(message sender)
  • 消息監聽者(message listener)
  • 消息檢查者(message checker)
  • 消息處理者(message handler)
  • 消息分解者(message cracker)
  • 消息映射者(message mapper)

  下面分別說明。

2.1 消息發送者和消息(message sender,message) 

  消息發送者其實只是在這里友情客串一下,它不在框架設計之內,由操作系統扮演這個勞苦功高的角色,它的工作是將消息發送到消息監聽者。在這里面隱含了一下最重要的角色,消息。其實剩余的所有角色說到底也只是死跑龍套的,真正領銜的是消息本身,比如窗口大小改變了的消息,按鈕被點擊了的消息等等,所有人都高舉旗幟緊密團結在它周圍進行工作。但消息本身只是一個很簡單的數據結構,因為再復雜的 GUI 系統,它的消息也不過是幾個參數,所以框架的實現重點在其它的角色。在此之前簡單地封裝一下消息,一個最簡單的封裝可能是這樣:

   1: // 消息封裝類
   2: class Message
   3: {
   4: public:
   5:     Message( UINT id_=0,WPARAM wparam_=0,LPARAM lparam_=0 )
   6:         :id( id_ )
   7:         ,wparam ( wparam_ )
   8:         ,lparam ( lparam_ )
   9:         ,result ( 0 )
  10:     {}
  11:
  12:     UINT      id;
  13:     WPARAM    wparam;
  14:     LPARAM    lparam;
  15:     LRESULT   result;
  16: };

  就這樣的我們的公司已經有了核心角色了。從概念上講,我們的這個基于消息的 GUI 框架已經完成了 99% 。然后我們可以以它為中心,按功能劃分進行詳細討論,一步步完成那剩余的 1% 的極富創意和挑戰的工作。在此之前,先得簡單解釋一下這幾個角色都各是什么概念。消息傳送者如上所述,將不在討論范圍內。

2.2 消息監聽者(message listener) 

  消息監聽者完成的工作是從操作系統接收到消息,消息是從這里真正到達了框架之內。最簡單的消息監聽者是一個提供給操作系統的回調函數,比如在 Windows 平臺上這個函數的樣子是這樣:

   1: //我是最質樸的消息接收者
   2: LRESULT CALLBACK windowProc( HWND window,UINT id,WPARAM wparam,LPARAM lparam );

  一個好 GUI 框架當然不能赤祼祼地使用這個東西,我們要在此之上進行面向對象的封裝。消息監聽者能想到的最自然的封裝模式是觀察者模式(Observer),這樣的模式下的監聽者實現看起來像這個樣子:

   1: //我是一個漂亮的觀察者模式的消息監聽者
   2: class MessageListener
   3: {
   4: public:
   5:     virtual LRESULT onMessage( Message* message ) = 0;
   6: };
   7:
   8: //監聽者這樣工作
   9: MessageListener* listener;
  10: window->addListener( listener );
  11:

  jlib2 和 VCF 的實現就是這種模式。但現實當中大多數框架沒有使用這種模式,比如 SmartWin++ 和 win32gui ,甚至沒有使用任何模式比如 MFC 和 WTL 。我想它們所以不采用觀察者模式,有些是因為框架整體實現的牽制,有的則可能是因為沒能解決某些技術問題。我們的 GUI 框架將實現觀察者模式的消息監聽者,所以這些問題我們后面也會遇到,到時候再詳述。

2.3 消息檢查者(message checker)

  消息檢查者完成的工作很簡單。當收到消息的時候,框架調用消息檢查者檢查這個消息是否符合某種條件,如果符合,則框架再調用消息處理者來處理這個消息,所以有點類似一個轉換者,輸入(消息),輸出一個(是/否)的值。最簡單的檢查者可能就是一個消息值的比較,比如:

   1:
   2: /最簡單的消息檢查者
   3: essage.id == /*消息值*/
   4:
   5: /比如
   6: essage.id == WM_CREATE

  展開MFC 和 ATL 的消息映射宏,可以看到它們的消息檢查就是用堆積起來的消息值比較語句完成。這就是消息檢查者最原始最自然最簡單的實現方式,但這種方式缺陷太多。我們的框架將實現一個自動化,具有擴展性的消息檢查者,后文詳細討論。

2.4 消息處理者(message handler)

  消息處理者是我們最終的目的。GUI 框架所做的一切努力都只是前期的準備,直到消息處理者運行起來那一刻,整個公司才算是真正地運轉起來了。消息處理者的具體實現可能是自由函數,成員函數或者其它可調用體,甚至可以是外部腳本,處理完畢可能需要給操作系統返回一個結果。最簡單的消息處理者可以就是條語句,比如:

   1: //消息處理
   2: alert( "窗口創建成功了!" );
   3:
   4: //返回結果
   5: message.result = TRUE;

  上面代碼中"顯示消息框"的動作就是一個消息處理,以上兩行代碼可視為消息處理者。最常見的消息處理者是函數,比如:

   1: //消息處理
   2: _handleCreated( message );

  代碼中的函數 _handleCreated 就是一個典型的消息處理者。消息處理者的實現難處在于,既要支持多樣性的調用接口,又要支持統一的處理方式。我們的框架將實現一個支持自由函數,成員函數,函數對象,或者其它可調用體的消息處理者,并且這些可調用體可以具有不同參數列表。后文將進行消息處理者的詳細討論。

  在這里有必要再說明一下。一個判斷語句的大括號之前(判斷部分)是消息檢查的動作,大括號之內(執行部分)是實際的消息處理。因此一個判斷語句雖簡單,卻包含消息檢查者和消息處理者,以及另外一個神秘的部分(見后文),一共三個部分。代碼像這樣:

   1: if ( //消息檢查者 )
   2: {
   3:     //消息處理者
   4: }

  比如下面的代碼:

   1: // message.id == WM_CREATE 是消息檢查者
   2: // _handleCreated( message )是消息處理者
   3:
   4: if ( message.id == WM_CREATE )
   5: {
   6:     _handleCreated( message );
   7: }
   8:

2.5 消息分解者(message cracker)

  消息分解者是為消息處理者服務的。不同的消息處理者需要的信息肯定不一樣,比如一個繪制消息(WM_PAINT)的消息處理者可能需要的是一個圖形設備的上下文句柄(HDC),而一個按鈕點擊消息(BN_CLICK)的消息處理者則可能需要的是按鈕的ID,它們都不想看到一個赤祼祼的消息杵在那里。從消息中分解出消息攜帶的具體信息,這就是消息分解者的工作。最簡單的消息分解者可能是一個強制轉換,比如:

   1: // WM_CREATE 消息參數分解
   2: CREATESTRUCT* createStruct = (CREATESTRUCT*)message.lparam;
   3:
   4: // WM_SIZE 消息參數分解
   5: long width  = LOWORD( message.lparam );
   6: long height = HIWORD( message.lparam );

  上面的的代碼雖然簡單但 100% 完成了消息分解的任務,所以它也是合格的消息分解者。我的框架將實現一個自動化,可擴展的消息分解者。后文將以此為目標進行詳細討論。

2.6 消息映射者(message mapper)

  消息映射者是最直接與框架外部打交道的部分,顧名思義,它的工作就是負責將消息檢查者與消息處理者映射起來。最簡單的映射者可以是一條判斷語句,這個判斷語句,如代碼所示:

   1: // if 語句的框架就是一個消息映射者
   2:
   3: // 消息映射者
   4: if ( /*消息檢查者*/ )
   5: {
   6:     /*消息處理者*/
   7: }
   1: // if 語句將消息檢查者 message.id==WM_CREATE 和消息處理者 _handleCreated(message) 聯系起來了
   2: if ( message.id == WM_CREATE )
   3: {
   4:     _handleCreated( message );
   5: }

  上面的代碼 的if 語句中,判斷的部分是消息檢查者,執行的部分是消息處理者。if 語句把這兩個部分組成了一個映射,這是最簡單的消息映射者。到這里可以發現,這個簡單的 if 語句有多不簡單。它低調謙遜但獨自地完成了很多工作,就像公司的小張既要寫程序,又要掃地倒茶,還義務地給女同事講笑話。MFC 和 WTL 的消息映射宏展開就是這樣的 if 語句。像 jlib2 那樣的框架,雖然處理者都虛函數,但在底層也是用 if 語句判斷消息然后來進行調用的。當然還有華麗一點的消息映射者,像這樣:

   1: // 華麗一點的消息映射者
   2: window.onCreated( &_handledCreated );

  這個 onCreated 也是一個消息映射者,在它的內部把 WM_CREAE 消息和 _handleCreated 函數映射到一起,這種方式最有彈性,但實現起來也比宏和虛函數都要困難得多。SmarWin++ 就是使用的這種方式,它的消息映射者版本看起來一樣的陽光帥氣,但內部實現有些細節稍嫌猥瑣。我們的 GUI 框架將實現一個看起來更美,用起來很爽的消息映射者像這個樣子:

   1: // 將消息處理者列表清空,設置為某個處理者
   2: // 可以這樣
   3: window.onCreated  = &_handleCreated;
   4: // 或者這樣
   5: window.onCreated.add( &_handleCreated );
   6:
   7: // 在消息處理者列表中添加一個處理者
   8: // 可以這樣
   9: window.onCreated += &_handleCreated;
  10: // 或者這樣
  11: window.onCreated.add( &_handleCreated );
  12:
  13: // 清空消息處理者列表
  14: // 可以這樣
  15: window.onCreated --;
  16: // 或者這樣
  17: window.onCreated.clear();

  值得說一下,這種神奇的映射者是接近零成本的,它沒有數據成員沒有虛函數什么都沒有,就是一個簡單的空對象。就像傳說中的工作能力超強,但卻不拿工資,不泡公司MM,甚至午間盒飯也不要的理想職員。在后文當中會具體詳述這個消息映射者的實現。

3 結尾廢話

  到目前為止我們的框架已經完成了 99% 。下篇準備開始寫最簡單的消息檢查者,但說實話我也不知道下一篇什么時候開始??纯瓷弦黄罩?,竟然是一年前寫的,這一年內發生的事情很多,但自己渾渾噩噩地的好像一眨眼就到了現在,看著 CPPBLOG 上的好多其它兄弟出的很多很有水準的東西,心里真是慚愧。昨天看了《2012》,現在心里還殘留有那種全世界在一間瞬間灰飛煙滅的震撼,2012年也不遠了,我也趕緊在地球毀滅之前加把油把這些日志寫完了吧。不管怎么樣,今天哥先走了,請不要迷戀哥。

評論共2頁: 1 2 

Feedback

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 18:21 by OwnWaterloo
占個沙發,對這個很好奇:

"值得說一下,這種神奇的映射者是接近零成本的,它沒有數據成員沒有虛函數什么都沒有,就是一個簡單的空對象。就像傳說中的工作能力超強,但卻不拿工資,不泡公司MM,甚至午間盒飯也不要的理想職員。"

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 18:24 by cexer
@OwnWaterloo
謝謝沙發,后面會詳細說說這個消息映射者的實現

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 18:38 by WXX
如果單就消息的派發,那么一個gui框架可以很容易實現,但是要很好的管理窗口之間的關系,很好的處理鍵盤加速鍵,鼠標等這些交互就麻煩了,考慮消息的過濾等。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 18:40 by OwnWaterloo
從這2行代碼來說:
window.onCreated.add( &_handleCreated );
window.onCreated.clear();


從UINT message 到 onCreate 的這個分派(dispatch)過程,無論怎樣都必定是一個運行時解析過程。
無論使用何種C++高級技巧,也不可能讓它變為編譯時dispatch。
因為message是一個運行時才能得到的值……
只是,這個dispatch的工作,可能不是由window所在的class完成,而是其他某個層次完成。
總之,這個層次一定存在,這個工作(運行時dispatch)一定免不了。
我猜得對嗎……


接下來,框架將message 分派到onXXX之后,客戶再將onXXX轉發(forwarding)自己的handler這個過程,我相信是可以編譯時確定的。
——因為我看過你以前的一些實現~_~

但是,編譯時確定,就意味著運行時不可更改。
如果要運行時可更改 —— 如上面2行代碼 —— 就一定需要一個"可調用體"(callable)來作handler的占位符(placeholder)。
是這樣嗎?
而C/C++里面,完全不占用一點空間的callable…… 是不存在的,對吧?


所以我很好奇window是如何零成本完成映射與轉發,并且是一個空對象的。
映射肯定需要在某個地方做,可能不是window。
運行時可更改轉發目的地而不使用數據, 好像…… 太不可思議了……


# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 18:48 by cexer
@OwnWaterloo
這個消息映射者的實現沒有數據成員,沒有虛函數存在,它其實就是一個調用,所以它也是有時間成本的。是的,所以我說是接近0成本,而不是真正的0成本。畢竟世界上沒有那樣傳說的員工。但如果好的編譯期可以輕松優化掉這個小小調用。

你說不可思議,那倒沒到那個境界哈。你可以看看《Imperfect C++》當中的method_property的實現,跟那個很類似,不過他的實現不符合C++標準,應范圍有限。

用這種消息映射者的方式,我也實現了值主義的屬性,比如:
window.size = Size( 100,100 );
Size size = window.size;

從理論上來說,也接近0成本的。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 18:53 by cexer
@OwnWaterloo
你說的“接下來,框架將message 分派到onXXX之后,客戶再將onXXX轉發(forwarding)自己的handler這個過程,我相信是可以編譯時確定的。
——因為我看過你以前的一些實現~_~”

你說的這個是以前另一個版本的框架了,跟這個完全不一樣了哦。你看那份代碼,它的消息映射是編譯期自動進行的,映射者,檢查者,分解者三個角色都是由一個東西全部完成的。我將寫的這個版本不一樣,是完全分離的實現,沒有那種編譯期映射的功能,但運行時映射可以獲得更大的擴展性。

可惜啊,以前那個版本的框架代碼我已經找不到了。你那里有?

你說的“以我很好奇window是如何零成本完成映射與轉發,并且是一個空對象的。映射肯定需要在某個地方做,可能不是window。運行時可更改轉發目的地而不使用數據, 好像…… 太不可思議了……”

看來你有點誤會我的意思了,肯定內存當中是有一個 std:map 之類的映射數據存在的。我說的0成本指的是 window.onCreated 這個成員的實現。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 18:57 by OwnWaterloo
@cexer
哦 原來是property模擬。

window.size
window.onCreated
是一個,呃,代理(proxy)對象?

size是一個代理,有一個=操作符和轉換函數:
operator=(const Size& s) {
this->window->setSize(s);
}
operator Size() const {
return this->window->getSize();
}

這個代理對象也是要占空間的吧?


呃…… 還是等你的源代碼與分析吧…… 不瞎猜了……
看來你也是難得抓到一個更新blog的閑暇……

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 18:58 by OwnWaterloo
@cexer
沒有完整的,就是你blog上發的一些片段……

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 19:01 by cexer
@OwnWaterloo
你說“哦 原來是property模擬?!?br>嗯就是這樣的。

你說“這個代理對象也是要占空間的吧?”
理論上來說不要占空間的,但實際是占用一個字節的。因為C++標準規定不允許存在0空間占用的成員變量,因為那會造成 &object.member1,&object.memeber2 兩個地址完全相同的情況。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 19:07 by cexer
@WXX
你說“如果單就消息的派發,那么一個gui框架可以很容易實現,但是要很好的管理窗口之間的關系,很好的處理鍵盤加速鍵,鼠標等這些交互就麻煩了,考慮消息的過濾等?!?
各有難處,但我覺得后者更容易一點,以前我也都做過一些。我覺得好的框架應該是在所有東西之前的。先有骨架,然后再說高矮胖瘦。長得是否高大健壯,陽光帥氣,就先看骨架如何了。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 19:08 by cexer
@OwnWaterloo
你說“沒有完整的,就是你blog上發的一些片段……”
我找找看,找到了也給你一分。那些片段隱藏了實現的,看不出什么有價值的東西。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 19:43 by OwnWaterloo
我去看了看Imperfect C++。因為是英文的,以前只看了一部分就停了……
我覺得前面都寫得不錯,ABI、mangling這些問題確實是很困擾……

但property…… 我覺得這并不算C++的Imperfection。
完全是被其他語言給蒙蔽了。

一門語言就應該有一門語言的樣子。
比如用C,如無絕對必要,就不應該模擬出虛函數與異常機制。
書里面提到的7種優勢,除了最后一種值得考慮,其他全都不值得稱作優勢。

property,C#中的,或者imperfect cpp 里面看起來的樣子,無非就是讓如下語法:
o.p( value ); o.set_p( value );
value = o.p(); value = o.get_p();

變成如下語法:
o.p = value;
value = o.p;

如果C++直接提供property的語法,那當然是好事。
但不提供,也不見得編程就進行不下去了,也沒有絕對必要去模擬出這種東西。


書中提到的7個好處,除了最后1個,其他都不是property帶來的好處。

1. 讀寫控制
非property一樣可以實現讀寫控制

2. 內外表示轉換
這依然是由Date的算法實現,而不是property的功勞。
不通過property,一樣可以實現time_t作為內部表示,對外提供int get_xxx()

3. 驗證
這個更搞笑了。
就在書中這句話的下面列出的代碼,就明顯說明對day的驗證是通過:
void Date::set_DayOfMonth(int day);
完成的。

完全沒有property的半分功勞。

4. 沒看太明白
書中的意思是, Date.Now是一個緩存,Date::get_Now()是一個計算?
其實,這里真正需要的是"兩種語意",而不是property。
這兩種語意是:
Date::getNow();
Date::updateNow();

書中只是將getNow的語意,用property來表現;get_Now同時完成updateNow和getNow。


5. 可用性?
We didn't have to write date.get_Month(), just date.Month.
少敲點鍵盤而已……
如果利用重載,用void Month(value)代表set,用value Month(void)代表get,少敲的,就是一個調用操作符……

6. 不變式?
沒看太明白。
沒有property,也可以維護object的不變式,是吧?
只要將數據hide,通過public interface去操作,就可以了。

7. 范型編程
我覺得就只有這個說到點子上了。 而這個也就是模擬property的真正的作用:改變語法。
template<typename P>
void distance(const P& p1, const P& p2 ) {
假設這是一個計算2個點歐式距離的函數。 它已經通過:
p1.x;
這種語法,而不是p1.x(); 這種語法寫成了。
}

如果新實現一個class, 想通過p1.x 這種語法訪問,同時還要有讀寫控制等, 就需要模擬出property。
這可能是property真正的用處。




換句話說,真的存在很多情況,使得我們"不得不"使用如下語法:
o.p = value;
value = o.p;

來代替如下語法:
o.set_p( value );
value = o.get_p();
嗎?

對這種情況,property模擬才是有價值的。


對很多情況,setter/getter的語法和property的語法都是可行的。
如果本身提供了property語法的語言,那使用2者之一都沒關系。
但C++沒有提供。對這種情況也非要去模擬出property的語法,就有點…… 為了property而property的味道了。


我繼續去看書中怎么實現的……
我以前了解的實現方式,每一個proxy至少得帶一個指針……
這消耗還是蠻嚴重的……


你怎么看?

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 19:57 by OwnWaterloo
我暈……
侵入式的property……

強大……

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 20:13 by cexer
@OwnWaterloo
你說“我去看了看Imperfect C++。因為是英文的,以前只看了一部分就停了……我覺得前面都寫得不錯,ABI、mangling這些問題確實是很困擾……我繼續去看書中怎么實現的……”
在C++里面屬性只是看起來很美的東西,其實用性和想像的有差距。你說得對,用方法能實現所有的功能,所以我的GUI框架已經去掉了屬性支持。

你說“我以前了解的實現方式,每一個proxy至少得帶一個指針……這消耗還是蠻嚴重的……”
不一定得帶指針的。比如《C++ Imperfect》里實現的屬性是不需要帶指針的,但它不被C++標準支持,應用有限制。我也實現了不需要帶指針的屬性,同時也是跟C++標準相容的,以后會說一下這個實現。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 20:31 by OwnWaterloo
@cexer
Imperfect C++中的差不多看明白了……
proxy是包含在object中,通過proxy在object中的offset得到object的地址……


# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 20:48 by 空明流轉
你還是講講2012比較好。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 22:02 by OwnWaterloo
@cexer
我想到一個幾乎沒有消耗的方案……
我再完善一下……

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 22:07 by cexer
@OwnWaterloo
你說“我想到一個幾乎沒有消耗的方案……我再完善一下……”
加油,出來了別忘了寫出來大家分享。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 22:08 by cexer
@空明流轉
好的,是這樣的,《2012》說的是地球毀滅的故事,好了我說完了。
哈哈!

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-15 22:33 by OwnWaterloo
@cexer
關鍵點在于:"如何通過proxy地址,得到object地址"。
 
1. 可以給proxy放入一個object的指針。每個proxy消耗一個指針的空間。
太浪費了……
 
2. Imperfect C++中利用offset來計算object的地址,使得每個proxy不含任何域。
但C++為了區別各個proxy,會給每個proxy進行填充,依然會造成消耗。
 
 
3. 避免proxy在Record中被填充
假設有如下記錄:
Record
{
        proxy1 property1;
        proxy2 property2;
        proxy3 property3;
        ...
}
其中proxy1,proxy2,proxy3, ...  是空結構。
 
Record r;
r.property1;
r.property2;
r.property3;
...
 
在C/C++中,如果想要property1、2、3、... 不占空間,也就是它們必須擁有相同的地址。
也就是說…… Record 是union……
 
 
4. 避免Record在class中被填充
即使這樣,整個union依然是會被填充……
所以,可以將class的數據和union放在一起,就真的不消耗任何空間了……
(如果類本身不需要數據,那這個類也是會被填充的,proxys依然沒有消耗多余的空間)
 
 
代碼:
class date
{
private:
        
struct second_proxy
        {
                
operator int() const
                {
                        
const void* o = this;
                        
int sec = static_cast<const date*>(o)->get_second();
                        printf(
"%d second_proxy::operator int(%p);\n",sec,o);
                        
return sec;
                }
                date
& operator=(int sec)
                {
                        
void* o = this;
                        printf(
"second_proxy::operator=(%p,%d);\n",o,sec);
                        date
* d = static_cast<date*>(o);
                        d
->set_second(sec);
                        
return *d;
                }
        };

public:
        union
        {
                time_t time_;
                second_proxy second;
        };

        date()
        {
                printf(
"date::date(%p);\n",(void*)this);
                time(
&time_);
        }

        
int get_second() const
        {
                
int sec = localtime(&time_)->tm_sec;
                printf(
"%d date::get_second(%p);\n",sec,(void*)this);
                
return sec;
        }

        
void set_second(int sec)
        {
                printf(
"date::set_second(%p,%d);\n",(void*)this,sec);
                tm t 
= *localtime(&time_);
                t.tm_sec 
= sec;
                time_ 
= mktime(&t);
        }
};

second_proxy hour和date的數據time_t time_放在同一個union中。
這樣,它們擁有相同的地址。
在second_proxy::operator int() const; 中,可以直接將this轉換到const date* ……
就這樣得到了…… object的地址……
 
 
在C中,必須給那個union取一個名字:
struct date
{
        union
        {
                time_t time_;
                second_proxy second;
        } must_have_a_name;
};
struct date date;
date.must_have_a_name.second; 必須這樣訪問……
 
C++中有匿名聯合,所以可以直接:
date d;
d.second;
 
 
這樣的壞處也很明顯……
客戶代碼可以訪問 d.time_;   什么都完蛋了……
 
 
我再想想怎么隱藏……
 

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 02:48 by OwnWaterloo
關于隱藏time_成員……
 
1.
class C
{
public:
        union
        {
                proxy1 property1;
                proxy2 property2;
                proxy3 property3;
                // private: // msvc和gcc這里是不能加入private的(具體要查標準)
        }
};
 
 
2.
class C
{
public:
        union
        {
                proxy1 property1;
                proxy2 property2;
                proxy3 property3;
        }
private:
        struct impl impl_; // 一旦不放在union中
        // properties就可能會被填充
};
 
 
仔細選擇union的位置,可能會起到減少總大小的效果,比如:
class C1 {
        char c_;
public:
        union { /* properties */ };
private:
        int i_;
};
class C2 {
        int i_;
        char c_;
public:
        union { /* properties */ };
};
 
因為char c_;的附近本來就會被填充,properties剛好占據那個位置。
 
 
如果這樣,就可能會很糟糕:
class C3 {
        char c_;
        int i_;
public:
        union { /* properties */ };
};
 
C++標準沒有說class的layout應該怎么安排。
每個access secion中的data,必須按聲明順序。
但不同access secion之間,可以打亂順序 —— 這個也許會有一些幫助。
 
第2種方案依然需要計算offsetof…… 這是很不靠譜的……
 
 
3.
作為base。
 
struct properties
{
        union
        {
                proxy1 property1;
                proxy2 property2;
                proxy3 property3;
                // ...
        }
};
 
class C : public properties
{
        // data
};
 
這樣,可以很容易計算出C的位置:
void property_proxy::operator=( ... ) {
        void* p = this;
        C* c = static_cast<C*>( static_cast<properties*>(p) );
}
 
編譯器會正確計算出C*,即使在多繼承下也行。比如:
class C : other , public properties {};
 
但是,在msvc和gcc上,properties 都不會執行空基類優化…… 很囧……
仔細安排data的布局(在這2款編譯器上,將小對齊的數據放前面),也會影響整個類的大小。
 
 
2和3的方案,如果能鉆到空子,就可以幾乎沒有代價的實現property。
空子就是說,那個class本來就有一些需要被填充的空洞……
 
如果沒有空可鉆……  或者沒有正確安排位置…… 會多占用一些大小。
額外大小不超過class中所有域中需要最大對齊那個成員的大小。
額外大小和properties的數量是無關的, 多少個properties都占用這么多額外大小, union嘛……
 
 
4.
還有一種方案。
其實上面的date,缺陷在于time_t time_; 這個成員會被外界訪問。
 
可以這樣:
class date {
        class impl {
                time_t time_;
                friend class date;
        };
public:
        union
        {
                impl impl_;
                proxy1 property1;
                proxy2 property2;
                proxy3 property3;
        }
        // ...
};
 
這樣,客戶代碼可以訪問的,只有impl_這個名字。
但是幾乎不能拿它作任何事情。
連它的類型名 —— date::impl —— 都是不可訪問的。
 
 
這個方案的缺陷就是 —— 通常必須從頭設計一個類型。
union中不能放非pod。只能從內建類型開始,構造一些類。
 
比如已經有某個非pod的類,C,想將C作為一個成員,實現Cex:
 
class Cex
{
        union
        {
                C impl_; // 不行
        }
};
 
只能用上面的property base或者property member……
 
 
5.
范化工作 
這個…… 再說吧……
 如果能有更好的布局方案…… 范化工作就白做了……
 

# re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

2009-11-16 09:42 by foxriver
針對游戲的每幀更新UI,這種消息結構是沒有什么太大價值,一堆回調繼承函數會迅速膨脹代碼量。

微軟用消息做UI最大的好處,是效率。可是當顯卡,CPU如此快速,為什么不尋找另外一種UI解決方式?

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 12:45 by 陳梓瀚(vczh)
之前我已經做過一個發在我的頁面上了,其實最麻煩的事,Windows所要求的標準界面行為API幾乎都沒實現,而且很雜,舉個例子:

如果有一個存在Default Button的窗口,你需要:
在沒有其他按鈕獲得焦點的情況下,將Default屬性設置給那個按鈕
在有按鈕獲得焦點的情況下,將Default屬性設置給有焦點的那個按鈕
按下Enter的時候,窗口自動激活帶Default屬性的按鈕的事件

還有譬如如何消除task bar上面的窗口按鈕等等,或者往菜單加上圖片(API絕對沒支持,全部都是owner draw上去的!!?。?,或者shortcut(菜單的Ctrl + C什么的,也是你自己寫上去而不是系統自帶的,而且你負責關聯他們),或者Tab轉移焦點啦(WS_TABSTOP完全沒用,那是給你實現轉移焦點的時候去獲取控件的屬性然后決定要不要把焦點給它),或者容器被disable之后上面的控件也要跟著顯示成disable一樣啦,還有Vista以下版本你把一個checkbox放在groupbox里面是黑掉的,微軟證實了這個是bug,不過他只在Vista修了。諸如此類,林林總總,你慢慢處理……

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 12:56 by 陳梓瀚(vczh)
@OwnWaterloo
光有屬性還不夠,一般來說一個完整的屬性應該是:

a=Object.Property;
Object.Property=b;
Object.Property.OnChanging+=functor;
Object.Property.OnChanged+=functor;

//僅本類可用
Object.Property.Self.OnChanging+=functor;
Object.Property.Self.OnChanged+=functor;

當一個Property是int的時候,你要重載完所有操作符,才能讓Object.Property1 + Object.Property2這樣子的表達式成立,這一點不知道你想到了沒。

所以為了提供更加好用的Property,你還得實現一個Ref<int>,讓Ref<int>同時兼容int&和&Object.Property,并且Ref<int>提供所有操作符重載。同時,如果Property和Ref<X>是一個類的話,最好帶上->以便訪問成員函數。

在C++提供property是一件吃力不討好的事情。還是罷了,之前試過,不值得。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 12:59 by 陳梓瀚(vczh)
@foxriver
請使用.NET的WPF,這就是“當顯卡,CPU如此快速”的時候的“另外一種UI解決方式”。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 13:12 by OwnWaterloo
@陳梓瀚(vczh)
我在上面的評論也說了,在C++中沒有絕對必要,還是不要模擬這個東西了。
沒有property,生活一樣可以繼續。


至于 o.p1 + p.p2, &p.p1, 等模擬,并不是重點。
proxy本來就和被proxy的對象有所區別。
視你需要模擬的程度來決定編程的復雜度,或者發現某種特性無法實現。

proxy的重點在于如何高效的在proxy中得到被proxy對象。
實現了這個功能點,才是其他op=,op T(), op +, ref proxy的前提 —— 這些技術基本屬于濫大街了 —— 去看vector<bool> —— 所以我對這些沒什么興趣。

之所以重新開始研究這個技巧,是因為Imperfect C++中提到了一種代價很低的方案。
我覺得這個有點意思,所以繼續深入了一下。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 13:19 by OwnWaterloo
@陳梓瀚(vczh)
還是用代碼說話吧……

上面的date的代碼,重點在于這2行:
union { /* ... */ };

哦,還有一行在我的測試代碼中,并沒有抄出來:
typedef int assume[sizeof(date)==sizeof(time_t)?1:-1];
這2行才是我想表達的重點 —— 沒有額外的空間消耗,實現property。


寫上面那些代碼,為的是展示上面那個重點,而不是下面這種爛大街的:
operator = (int);
operator int() const;
這些技術早就被人討論得七七八八了,相當無聊……


這一點不知道你看出來了沒?

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 13:35 by 陳梓瀚(vczh)
@OwnWaterloo
當且僅當好用,才有繼續考慮有沒有消耗的必要。一個高效的、會制造麻煩的解決方案一般是不被接受的。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 13:45 by cexer
@OwnWaterloo
謝謝你分享你的思考,真的很有創意。建議你可以把你的思考寫到你的博客里,讓更多的人看到。

@陳梓瀚(vczh)
高效的,不會制造麻煩的東西,也是從不高效的,會制造麻煩的東西進化來的。所以我覺得只要在思考都是有價值的。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 13:50 by OwnWaterloo
@陳梓瀚(vczh)
1.
如果一個property需要消耗一個指針的代價,我認為實現得再好用都是不可接受的…… 高得有點離譜。
很容易一個類的property的消耗比類本身還要高。
[data of instance]
[pointer]
[pointer]
[pointer]
[pointer]
....
而且所有pointer都指向同一個地方……

而且上面也提到,真正需要模擬出property的,是類似于point、vector、matrix這種類。
p.x比p.x()更自然。
讓這種類附加額外數據,也是讓人很難接受的。


2.
是否好用,你可以另外開文章討論。
只是你提到的那些使得proxy好用的技術,都提不起我的興趣。
理由很簡單 —— 這種文章太多,寫出來都不好意思和人打招呼。

我關心的僅僅是proxy如何高效得到被proxy對象 —— 實現proxy的前提。

我以前見過的,包括我自己想到的,都是上面one property one pointer的方案。imp cpp中提到的讓我確實有眼前一亮的感覺。
而 op T(), op= (T), ref T op + 這些技術,只讓我覺得漲眼睛 —— 看太多,早膩歪了。


如果你的重點依然在proxy如何更仿真上,可能很難提起我的興趣。
如果你有興趣思考proxy如何得到被proxy的object上,或者有一些方案,想法,歡迎分享出來~_~



"當且僅當好用,才有繼續考慮有沒有消耗的必要。一個高效的、會制造麻煩的解決方案一般是不被接受的。"
這句話絕對說得過于絕對了。
如果你用的是C++、而不是C#,效率絕對不是可以不掛在心上的事情。
為什么你要用C++,而不是用編程更方便,語法糖更多的C#?好好想想吧。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 14:02 by OwnWaterloo
@cexer
我再想想如何做得更范化一些。
減少需要編寫property的人的重復勞動。

但是遇到了困難……

手工寫union base, union member沒什么問題。
要將其做成模板…… 會遇到先有雞還是先有蛋的問題……

比如:
proxy< ..., getter , ... >

實例化一個property:
proxy< ..., &date::get_second, ... >; 這里需要date的定義?

而date的定義無論是union base,union member又需要先實例化proxy< ... &date::get_second, ... >;……
很囧……


手工寫沒問題是因為:
struct second_proxy {
這里僅僅聲明其布局就可以了
};

然后定義date類

然后實現proxy::op ...
這里才需要引用到date::&getter


我再想想……


# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 14:50 by 俠客西風
準備把你的博客加到我的友情鏈接里,

很喜歡您的風格...

呵呵,

是不是好多好多程序員都很像啊,

還是好多本來就很像的人都選擇了程序員這個

獨特的
浪漫的
理性的
(傾向于)創意的
...

職業啊

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 15:26 by 陳梓瀚(vczh)
@OwnWaterloo
效率不是一個可以忽略可用性的理由。我們需要的是一個在效率和外觀上都達到一定高度的想法,而不是一個因為效率而造成麻煩的想法。即使是C++,我們也要把它變得更方便。而且剛才的那些操作符重載其實跟你那個union沒有沖突,這無論有興趣與否,都是一個要解決的問題。如果別人解決了一個帶多余指針的好用的屬性,你解決了一個不帶多余指針的不好用的屬性,那都是沒意義的。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 15:28 by 陳梓瀚(vczh)
@OwnWaterloo
不過用了union,而跟你剛才那樣一個屬性就要定義一個類,而不是一種類型的屬性定義一個類(或者干脆就只有少數幾個屬性的類然后到處使用——這個要求比較高了一點),我覺得比多一個指針更加不能接受。運行時代價跟開發代價都是需要一并考慮的。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 15:29 by 陳梓瀚(vczh)
@OwnWaterloo
在關心程序的質量的同時,也要同時關心一下如何將廣大程序猿在C++的水深火熱之中解放出來。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 15:59 by OwnWaterloo
@陳梓瀚(vczh)
"如果別人解決了一個帶多余指針的好用的屬性,你解決了一個不帶多余指針的不好用的屬性,那都是沒意義的。"

我那個是沒有意義的?
我那個不可以繼續實現你所說的那些花哨的用法嗎?(為什么說花哨,我下面會說明)

我已經說過很多次了,"通過proxy得到被proxy的對象"是實現proxy的前提。
必須先滿足這個前提才能繼續實現proxy的其他技巧。

用union會影響實現其他技巧嗎? 依然可以。
難道我要把所有我會的技巧都展現一下嗎?
我不喜歡這樣[color=red]顯擺[/color]。
或者說,我也喜歡顯擺;但[b]我不喜歡顯擺[color=red]眾人皆會[/color]的技巧[/b]。
分享一些別人明顯了解的技術,只會[b][color=red]漲[/color]別人眼睛[/b]。


我只想展示如何高效實現proxy所需前提這一技巧,如是而已。



btw:為什么我說你那些是花哨的技巧。

首先引入一個概念:"總線式的設計"。

假設你需要實現一個函數f,f需要一個字符串輸入(暫不考慮wchar_t)。
如何設計這個輸入參數???

1. f(const char* c_style_string);
2. f(const char* first,ptrdiff_t length);

兩者取其一,可能1用得更多一些。

然后,其他所有的String,都向這個"總線" —— const char*靠攏。
比如std::basic_string要提供c_str(), CString要提供operator const char*()。

為什么要這么做?
1. 避免API的暴增。 直連線是平方增長,而總線是線性增長。
2. 有些時候,必須這樣
對fopen(const char*)你可以繼續直連線:
fopen(const std::string& );
fopen(const CString& );

對一個類:
class C {
open(const char* );
};

C不是你寫的,你不可能為C加入一個直連線。
你只能繞彎:
open( C& o, const std::string& );
open( C& o, const CStirng& );


回到你說的ref<T>。

假設有一個函數:
f(int& );
你可以為它直連線。


假設有一個類,依然不是你寫的,它只接受int &
class C
{
f(int & );
}

C2有一個屬性i。
class C2
{
property int i;
}

你要怎么給你的用戶解釋:
C c;
C2 c2;
c.f( c2.i ); 是行不通的,必須
f( c, c2.i ); ???

你能將所有api的匯合點都直連線嗎? 你做不到。


本來模擬屬性就是一個很無聊的事情了。
你告訴用戶,這就是一個C#中的 get; set; 就ok了,別想著要其他怎么用。
C#中可以 &o.p嗎???



"運行時代價跟開發代價都是需要一并考慮的。"
你看你是否自相矛盾了。
將線性開發代價的總線形式,轉化為平方開發代價的直連線形式。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:07 by OwnWaterloo
@陳梓瀚(vczh)
"不過用了union,而跟你剛才那樣一個屬性就要定義一個類,而不是一種類型的屬性定義一個類(或者干脆就只有少數幾個屬性的類然后到處使用——這個要求比較高了一點),我覺得比多一個指針更加不能接受。運行時代價跟開發代價都是需要一并考慮的。"

先說運行代價和開發代價。
C程序員可能會因為效率,不使用qsort。 他們都是傻子嗎?

"實現屬性占用的額外空間大小與屬性的數量無關",這難道真的不值得你為其多花點功夫嗎?
這是O(n)到O(1)的優化!是不值得的?!


而且我比較懷疑你所說的"一種類型的屬性定義一個類"是一種[color=red]侵入式[/color]的實現方式。


需要實現屬性的類本來就不應該很多。
我也想如何做得更范化一些, 但目前成效不大。

就像cexer說的"高效的,不會制造麻煩的東西,也是從不高效的,會制造麻煩的東西進化來的。"
如果一開始就采用指針保存,那是一輩子就沒辦法優化了。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:08 by 陳梓瀚(vczh)
@OwnWaterloo
舉個簡單的例子,總線形式的開發會讓string有一個c_str(),而不是讓string消失。我不知道你知不知道解決方案是什么樣子的,但是舉個例子出來總是要告訴別人說,“哦,這個東西可以這么這么做,只是我懶的寫完整”而不是沒說完。

模擬屬性一點都不無聊,因為一個可以監聽和撤銷屬性變化的系統非常容易擴展,總比你一個類都來一個listener或者就像api一樣所有的消息的格式都很混亂的好。特別對于開發GUI的庫。當然這就不僅僅是如何響應operator=的問題了。允許阻止變化的listener總是有開銷的,所以你無法杜絕它。這也是我第一次舉例子的時候就說出來的一個可以認為是“需求”的東西。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:09 by 陳梓瀚(vczh)
@OwnWaterloo
至于說“如果一開始就采用指針保存,那是一輩子就沒辦法優化了”,這是不對的。這跟接口無關,也就是你一個property的實現怎么變化,this指針放的地方換掉了,調用的代碼都是不用改的。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:14 by 陳梓瀚(vczh)
@OwnWaterloo
“需要實現屬性的類本來就不應該很多。”也不是很正確,當然這要看情況了。在我看來,每一個控件都要實現可監聽的屬性,這些屬性用事件暴露出來。你愿意設計完屬性之后從頭設計一次事件的命名和參數什么的好,還是愿意把它們綁定到屬性里面去?這樣的話開發控件也不需要考慮提供的事件完整不完整的問題了。因為控件的狀態都在屬性里面,屬性變更就是狀態變更,也就都有事件了。這樣做的話設計出來的控件就不會有winapi那個消息(我們都知道很難清洗化)的影子了,會有一個十分漂亮的設計出來。一般winapi封裝出來的東西都是在PC上跑的,你會在意一個textbox占了1k還是1.1k內存嗎?在這里多一個指針少一個指針根本不是問題。

特別是對于point,那就是個變量好了,point沒有行為,所以不需要屬性。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:15 by OwnWaterloo
@陳梓瀚(vczh) "在關心程序的質量的同時,也要同時關心一下如何將廣大程序猿在C++的水深火熱之中解放出來" 我展示的,只是"proxy得到object"的技術。 你可以繼續展示proxy得到object之后的"解救廣大C++程序員于水深火熱之中"的其他技術。 但說實話,我不覺得那些技術真的能救人于水火。
真正救人于水火的,不是玩弄高級技巧,而是設計清晰的接口。
還是上面的例子: C c; C2 c2; c.f( c2.i ); 不能這樣做,只會讓人感到困擾。 如果i不是屬性,而是成員: c.f( c2.i() ); 不行是很容易理解事情 —— 臨時對象不能綁定到非const引用上。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:21 by 陳梓瀚(vczh)
@OwnWaterloo
所以這就是Ref<>的意義了嘛,【在你的系統或由其擴展的系統里面】讓你的參數的引用都能接受屬性,同時也能接受舊的引用。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:27 by OwnWaterloo
"舉個簡單的例子,總線形式的開發會讓string有一個c_str(),而不是讓string消失。我不知道你知不知道解決方案是什么樣子的,但是舉個例子出來總是要告訴別人說,“哦,這個東西可以這么這么做,只是我懶的寫完整”而不是沒說完。"

我相信看的人能明白我代碼中的重點在那里。
我不明白的是你為何看不明白,即使在我說了之后。

我再說一下吧,如果要讓我說"這個property"還可以這樣玩、又可以那樣玩,我會覺得很掉價……
一方面因為我現在本來就不喜歡過分玩弄語言特性。
另一方面,這些技術實在是說得太多……


如果一定要把所有細節都展現出來,不知道評論的字數會不會夠。
而且,展現到什么程度才可以停止?

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:32 by OwnWaterloo
"模擬屬性一點都不無聊,因為一個可以監聽和撤銷屬性變化的系統非常容易擴展,總比你一個類都來一個listener或者就像api一樣所有的消息的格式都很混亂的好。特別對于開發GUI的庫。當然這就不僅僅是如何響應operator=的問題了。允許阻止變化的listener總是有開銷的,所以你無法杜絕它。這也是我第一次舉例子的時候就說出來的一個可以認為是“需求”的東西。"

模擬屬性就是很無聊的工作。
你試著回答一個問題:”這里一定需要operator=嗎? 可以使用命名函數嗎?
如果回答是“是” ,那這里就寧可采用命名函數,而不是屬性。
絕大部分回答都應該是“是”。所以這根本不算一個需求。


從我的直覺上來說,模擬屬性使用的價值并不大。沒有多少場合非屬性不可。
如果你可以想到一個“不是”的例子,請列舉出來。
也許我的直覺是錯的。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:32 by OwnWaterloo
"模擬屬性一點都不無聊,因為一個可以監聽和撤銷屬性變化的系統非常容易擴展,總比你一個類都來一個listener或者就像api一樣所有的消息的格式都很混亂的好。特別對于開發GUI的庫。當然這就不僅僅是如何響應operator=的問題了。允許阻止變化的listener總是有開銷的,所以你無法杜絕它。這也是我第一次舉例子的時候就說出來的一個可以認為是“需求”的東西。"

模擬屬性就是很無聊的工作。
你試著回答一個問題:”這里一定需要operator=嗎? 可以使用命名函數嗎?
如果回答是“是” ,那這里就寧可采用命名函數,而不是屬性。
絕大部分回答都應該是“是”。所以這根本不算一個需求。


從我的直覺上來說,模擬屬性使用的價值并不大。沒有多少場合非屬性不可
如果你可以想到一個“不是”的例子,請列舉出來。
也許我的直覺是錯的。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:34 by 矩陣操作
終于能看到一篇提起興趣的文章了。

我也寫過一個類似GUI框架,很lite,相當lite。主要也是因為引擎需要小型一個窗口系統。
風格上也是模擬了NET的方式,實現了一個Application::run(&win);這樣的玩意。window的消息映射上沒有去追求用標準CPP,直接用了VC特有的__event,__hook這些東西。
期待博主進一步討論!

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:41 by 陳梓瀚(vczh)
@OwnWaterloo
其實舉過了嘛,textBox.Text.OnChanging+=a_functor_that_want_to_check_the_input;你覺得在一個【上百個】屬性的Form(你不要告訴我一個窗口的狀態很少)里面,你打算怎么處理屬性和事件之間的關系,我一直都在強調這一點,而不是語法上的問題,operator=其實也是個例子,我沒有強調說要不要有個名字之類的東西。

另外,不要覺得什么掉價不掉價的,技術沒有貴賤之分,不要搞三六九等。話說我自己在搞語言設計和實現。從來跟這些property什么的關系不大,這估計是職業病。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:42 by OwnWaterloo
"當然這要看情況了。在我看來,每一個控件都要實現可監聽的屬性,這些屬性用事件暴露出來。你愿意設計完屬性之后從頭設計一次事件的命名和參數什么的好,還是愿意把它們綁定到屬性里面去?這樣的話開發控件也不需要考慮提供的事件完整不完整的問題了。因為控件的狀態都在屬性里面,屬性變更就是狀態變更,也就都有事件了。這樣做的話設計出來的控件就不會有winapi那個消息(我們都知道很難清洗化)的影子了,會有一個十分漂亮的設計出來。"

消除winapi的影子,一定需要屬性嗎?命名函數不可以嗎?
如果語言本身提供屬性,那使用兩者之一都沒什么問題。
如果語言本身不提供,為何在可以使用命名函數的地方,非要去使用屬性???



"一般winapi封裝出來的東西都是在PC上跑的,你會在意一個textbox占了1k還是1.1k內存嗎?在這里多一個指針少一個指針根本不是問題。 "
在gui上,你的說法是正確的。你甚至可以使用boost::any + boost::tuple來得到一個類似python的開發方式。

我討論的是"proxy得到object", 請不要將討論窄化gui上。
作為一個庫(非gui庫)的提供者,效率不是最主要的,但無端的損害(大量損害)就是不可接受的了。
假設某天你真的需要將proxy應用到critical的場景,你就會后悔當初沒有去考慮這個問題了。


"特別是對于point,那就是個變量好了,point沒有行為,所以不需要屬性。"
需要的。我舉個例子:
point
{
        property double x { get;set; }
        property double y { get;set; }
        property double radius { get; set; }
        property double seta { get; set; }
};

客戶可以用直角坐標或者極坐標去操作這個點。
點的內部表示客戶無須關心。
這樣的點,就需要行為。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:43 by 陳梓瀚(vczh)
@陳梓瀚(vczh)
至于其他的例子,你可以參考WPF強大的布局功能是怎么【under control】的,用傳統的方法根本沒法讓程序變得清晰。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:45 by 陳梓瀚(vczh)
@OwnWaterloo
【窄化】不是問題。GUI是一個龐大的東西,一旦需要了,那就有做的必要,就算除了GUI以外的所有系統都不能從這個property機制上獲得好處,也不是不搞的理由。而且讓一個例子舉現在一個范圍里面可以讓我們都少想很多東西。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:46 by 陳梓瀚(vczh)
@OwnWaterloo
行為指的是受改變的時候發生的事情,而不是你能從一個數據里面計算出什么東西。坐標轉換是映射,點在任何情況下都不可能因為你設置了一個什么坐標而做了一些什么事情,因此沒有行為。當然cache不同坐標系下的結果例外,這不是重點。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:49 by 陳梓瀚(vczh)
@OwnWaterloo
其實舉過了嘛,textBox.Text.OnChanging+=a_functor_that_want_to_check_the_input;你覺得在一個【上百個】屬性的Form(你不要告訴我一個窗口的狀態很少)里面,你打算怎么處理屬性和事件之間的關系,我一直都在強調這一點,而不是語法上的問題,operator=其實也是個例子,我沒有強調說要不要有個名字之類的東西。

另外,不要覺得什么掉價不掉價的,技術沒有貴賤之分,不要搞三六九等。話說我自己在搞語言設計和實現。從來跟這些property什么的關系不大,這估計是職業病。

【剛才看你在我之后刷了,也不知道看到沒有,所以再復制一次。如果你看過了就忽略】

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 16:56 by OwnWaterloo
【其實舉過了嘛,textBox.Text.OnChanging+=a_functor_that_want_to_check_the_input;你覺得在一個【上百個】屬性的Form(你不要告訴我一個窗口的狀態很少)里面,你打算怎么處理屬性和事件之間的關系,我一直都在強調這一點,而不是語法上的問題,operator=其實也是個例子,我沒有強調說要不要有個名字之類的東西。 】

textBox.Text.OnChanging+=a_functor_that_want_to_check_the_input;
你覺得改成這樣,可接受不?
textBox.Text.OnChanging.add( a_functor_that_want_to_check_the_input );
textBox.Text.OnChangingSet( a_functor_that_want_to_check_the_input );
textBox.Text.OnChangingAdd( a_functor_that_want_to_check_the_input );

C++就是C++,沒有必要模擬.net那一套。


【另外,不要覺得什么掉價不掉價的,技術沒有貴賤之分,不要搞三六九等。話說我自己在搞語言設計和實現。從來跟這些property什么的關系不大,這估計是職業病?!?/div>
因為我覺得通過union來達到property的額外空間與property數量無關這個技巧可能是新奇的。我沒有在其他地方看到過,也是我自己想出來的。

而proxy的那些技巧,我已經看太多,說不定大家都知道,我又何必多次一舉?
而且很多都不是我獨立想出來的。
所以我覺得說出來很掉價……

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:01 by OwnWaterloo
【至于其他的例子,你可以參考WPF強大的布局功能是怎么【under control】的,用傳統的方法根本沒法讓程序變得清晰。】

我不懂wpf。
不通過屬性,而是命名方法,可以做到讓程序清晰么?

如果只是從:
textBox.Text.OnChanging+=a_functor_that_want_to_check_the_input;

變化到:
textBox.Text.OnChanging.add( a_functor_that_want_to_check_the_input );
textBox.Text.OnChangingSet( a_functor_that_want_to_check_the_input );
textBox.Text.OnChangingAdd( a_functor_that_want_to_check_the_input );

我覺得依然很清晰。
也許前者對C#轉到C++的程序員來說,前者會很親切。
但后者才是大部分C++程序員的每天都在使用形式。

你不需要給你的用戶培訓“C++中如何模擬property”這種細節,他就可以使用你的api。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:01 by cexer
@OwnWaterloo
@陳梓瀚(vczh)
討論了那么多,突然發現怎么兩位同志的觀點都扭轉了?
“C++中實現屬性”這個問題也是討論過千百遍的了,而且注定是誰都無法說服誰的問題,因為它不純粹是一個技術問題。很大程序上和性格喜歡相關。就好像實用主義點的程序員,肯定覺得這樣費盡力氣去模仿不值得,但藝術氣質一點的程序員,覺得屬性的語法看起來很漂亮。然后各自用自己的偏好去說服對方,肯定不能成功。
我個人的觀點是,有總比沒有好。存在即合理,要不然怎么會那么多的語言提供語言級的屬性支持了。C++當中比這個急迫要解決的問題還很多,但可以預見,屬性這東西在未來的某個C++標準當中一定會出現的,它確實有著不可磨滅的價值。

但你們的討論里各自的觀點都很有道理,里面包含的有技術含量的思考很多,我看過也很有收獲。所以請你們在友好和諧的氣氛當中繼續嘗試說服對方吧,無論是什么樣的討論都是思想的碰撞,也是學習的很好的方式。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:03 by cexer
@矩陣操作
謝謝,很高興又認識志同道合的朋友。以后請多指教!

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:06 by OwnWaterloo
【【窄化】不是問題。GUI是一個龐大的東西,一旦需要了,那就有做的必要,就算除了GUI以外的所有系統都不能從這個property機制上獲得好處,也不是不搞的理由。而且讓一個例子舉現在一個范圍里面可以讓我們都少想很多東西?!?div>
就算這種O(n)到O(1)的優化因為它太復雜而永遠用不上,【也不是不搞的理由】。
我這么說對嗎?

討論如何優化就是討論如何優化。
沒有必要將這種討論限制到某個范圍內,然后說在這個范圍內不需要這種優化。

你不能預見所有場合都不需要這種優化。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:07 by cexer
@俠客西風
呵呵謝謝你能喜歡,其實我對技術的探求不是很深,只是迷戀于一些更簡單更表面的東西。做技術的人其實這樣不是很好。
你就不要準備了,直接把我加進去吧。但我不知道怎么加友情鏈接,在后臺加過好像沒成功。
很高興認識你!

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:10 by 陳梓瀚(vczh)
@OwnWaterloo
我并沒有反對你優化,我只是說“你覺得在一個【上百個】屬性的Form里面,你打算怎么處理屬性和事件之間的關系”。正好本篇是談GUI,因此舉這么個例子我覺得挺合適的。那我就這么問吧,我以為你是知道我在說什么的:在一個包含請求回調doing和響應回調one(+=也好,.Add也好)的屬性,怎么避免開銷?

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:11 by OwnWaterloo
【行為指的是受改變的時候發生的事情,而不是你能從一個數據里面計算出什么東西。坐標轉換是映射,點在任何情況下都不可能因為你設置了一個什么坐標而做了一些什么事情,因此沒有行為。當然cache不同坐標系下的結果例外,這不是重點。】

可能我們對“行為”這個詞有不同的理解。 那我們不用這個詞了。

對剛才說的點的例子。半徑絕對不能直接讀寫對吧?
所以必須將實現隱藏起來(實現可能是以直角坐標存儲), 通過setter/getter去操作,以維護點的不變式 —— 半徑不可能小于0。

我說的就是這個意思。

如果你需要可按極坐標操作的點,就不能直接暴露數據,必須使用setter/getter或者property。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:13 by 陳梓瀚(vczh)
@陳梓瀚(vczh)
話說回來,其實我覺得你后面的重點已經變成了operator=和operator+=與set_和.Add的關系了,這個我不想討論,因為我也覺得無所謂,用哪個都好,只要

1:先做的事情寫在前面,后做的事情寫在后面,要花時間看清楚。
2:不要造成括號的嵌套,難以維護。
舉個例子,挑選列表里面所有偶數的數字然后平方,如果你寫成square(even(a_list))就不滿足1和2。
【注意,上面兩點你看過就算了,我不討論好跟不好,因為跟主題沒關系】

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:15 by 陳梓瀚(vczh)
@OwnWaterloo
對于你的點的例子,我傾向于CalculateRadius(your_point)。你不能否認人們總是下意識地認為獲取屬性比調用函數的效率要高,所以那些真的做了事情,又不改變狀態,又只讀的屬性變成函數比較好。不過我還是想跟你討論“在一個包含請求回調doing和響應回調one(+=也好,.Add也好)的屬性,怎么避免開銷”而不是在這里要不要用只讀的屬性。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:17 by 陳梓瀚(vczh)
@cexer
我跟OwnWaterloo就借著你的地盤版聊了哈

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:19 by OwnWaterloo
【我并沒有反對你優化,我只是說“你覺得在一個【上百個】屬性的Form里面,你打算怎么處理屬性和事件之間的關系”。正好本篇是談GUI,因此舉這么個例子我覺得挺合適的。那我就這么問吧,我以為你是知道我在說什么的:在一個包含請求回調doing和響應回調one(+=也好,.Add也好)的屬性,怎么避免開銷?】

首先,上百個屬性是否可以改為上百個×2的getter和setter?
setter不一定難用,setter還可以鏈式表達呢。

對比:
1. w.p1(v1).p2(v2).p3(v3) ...
2. ( (w.p1 = v1).p2 = v2 ).p3 = v3 ...
3.
w.p1 = v1;
w.p2 = v2;
w.p3 = v3;


其次,如果真要模擬property。
我真的沒看明白你說的那個需求是什么…… 囧


# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:23 by OwnWaterloo
【我跟OwnWaterloo就借著你的地盤版聊了哈】

要不我們開一個論壇吧?google groups或者群?
群的聊天資料可能不容易搜出來……

toplangauge越來越水了……

把cppblog上的人都招集起來?

很高興和你們討論問題~_~
如果我語氣有過激之處,請不吝指出,或者多多包涵一下。

有人響應沒……?

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:26 by 陳梓瀚(vczh)
@OwnWaterloo
我覺得這里挺好的,其他地方都會水- -b就是cppblog還沒那么多亂七八糟的帖子。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:28 by 陳梓瀚(vczh)
@OwnWaterloo
屬性變化就是狀態變化嘛,你要監聽一個狀態的變化,還要控制一個狀態變化的權限,用屬性+事件的組合是很常見的。也就是說,一個屬性都要自動提供Changing和Changed兩個事件。嘗試把它做得高效,就是我之前的需求了。

這才是屬性的本質概念,跟語法什么的關系并不大。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:35 by cexer
@陳梓瀚(vczh)
你說“我跟OwnWaterloo就借著你的地盤版聊了哈”

絕沒問題,歡迎有自己思考的同志來此版聊,如果能切緊GUI框架的主題就感謝了哈,你們的討論能使我的博文增色不少。聊完不要走,此處管飯。

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:43 by 溪流
哈,關注~

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:43 by OwnWaterloo
1:先做的事情寫在前面,后做的事情寫在后面,要花時間看清楚。 
2:不要造成括號的嵌套,難以維護。 
舉個例子,挑選列表里面所有偶數的數字然后平方,如果你寫成square(even(a_list))就不滿足1和2。 
【注意,上面兩點你看過就算了,我不討論好跟不好,因為跟主題沒關系】

不能算了……
text.onchange_set( handler1 ).onchange_add( handler2 ).onchange_add( handler3 );
滿足上面的2點嗎?


對于你的點的例子,我傾向于CalculateRadius(your_point)。你不能否認人們總是下意識地認為獲取屬性比調用函數的效率要高,所以那些真的做了事情,又不改變狀態,又只讀的屬性變成函數比較好。
點的例子是因為你說point不需要行為而舉出的反例。

我也傾向于:
point
{
        double x() const;
        void x(double xx);
        double r() const;
        void r(double rr);
        // ..
};

我也沒有假設get_r( p ); 和 p.r() 或者p.r到底何種效率更高。
這取決與內部表達是直角坐標還是極坐標。

這些都不是重點。重點在于:
1. r絕對不可以是裸域 —— 它需要維護不變式
2. 如果你打算將x、y、r、a由函數變為屬性,4個指針的開銷你覺得是否合適?

btw:這4個屬性都可以是讀寫的,并不存在x、y讀寫,r、a只讀一說。


不過我還是想跟你討論“在一個包含請求回調doing和響應回調one(+=也好,.Add也好)的屬性,怎么避免開銷”而不是在這里要不要用只讀的屬性。
我沒看明白,能否再詳細一點?

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:46 by cexer
@OwnWaterloo
他少打一個字母!done

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 17:57 by OwnWaterloo
@cexer
絕沒問題,歡迎有自己思考的同志來此版聊,如果能切緊GUI框架的主題就感謝了哈,你們的討論能使我的博文增色不少。聊完不要走,此處管飯。

別管飯了……  正好有一個懸了很久的關于ATL/WTL的疑問,你管管吧~_~

是這樣的,ATL/WTL中提到一種atl-style繼承。
template<typename D>
class B
{
        void f1() { static_cast<D*>(this)->f1_do(); }
        void f2() { static_cast<D*>(this)->f2_do(); }
        void f1_do() { printf("B\n"); }
        void f2_do() { printf("B\n"); }
        
};

class D : public B<D>
{
        void f1_do() { printf("D\n"); }
};

D d;
d.f1();
D::f1不存在 -> 調用B::f1 -> 轉型為D* -> D覆蓋了f1_do -> 調用D::f1_do ->輸出"D"
d.f2();
D::f2不存在 -> 調用B::f1 -> 轉型為D* -> D::f2_do不存在 -> 調用B::f1_do->輸出"B"

是這樣嗎?


然后,問題就來了 …… 這個樣子,除了將代碼寫得更復雜以外,相比下面的寫法,有什么區別么??

class B
{
        void f1() { printf("B\n"); }
        void f2() { printf("B\n"); }
};

class D : public B
{
        void f1() { printf("D\n"); }
};

D d;
d.f1(); "D"
d.f2(); "B"


# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 18:02 by OwnWaterloo
@cexer
ATL-style繼承,提供的語意是:
基類提供默認實現,派生類繼并覆蓋自己需要的函數。
是這樣嗎?
除了這個,還有其他功能嗎?

但這個功能,不正好就是非ATL-style繼承 —— 普通繼承提供的語意嗎……
為什么要繞這么大一個圈子?
能得到什么好處? header-only?

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 18:06 by OwnWaterloo
@陳梓瀚(vczh)
屬性變化就是狀態變化嘛,你要監聽一個狀態的變化,還要控制一個狀態變化的權限,用屬性+事件的組合是很常見的。也就是說,一個屬性都要自動提供Changing和Changed兩個事件。嘗試把它做得高效,就是我之前的需求了。 

這才是屬性的本質概念,跟語法什么的關系并不大。

我覺得你又開始繼續扯遠了……
本來不是在討論“proxy的實現技術”“property和命名函數”么……

anyway,換個話題也好。
不過我真沒看明白你的需求是怎樣的……

這個需求是否高效,與“是否使用union”有關系嗎?
與“property vs 命名函數”有關系嗎?
是一個新話題吧?


能詳細說說你的需求么?

# re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

2009-11-16 18:08 by cexer
@OwnWaterloo
我的理解是所謂的ATL-Style,其實是用模板在編譯期手工模擬的一種多態,ATL 的實現當中大量地使用了這種方式,目的就是為了輕量級,幾乎沒用過虛函數。
我覺得這種手法的作用不僅僅限于此,因為可以結合其它的編譯期技術,實現很多虛函數難以達到的功能,我實現 GUI 框架的時候也用到很多這種東西,以后的說明中應該會遇到。

評論共2頁: 1 2 
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美一区二区国产| 欧美中文字幕视频| 91久久视频| 欧美精品一区二区三区在线看午夜| 亚洲免费av观看| 一本色道久久综合亚洲精品不| 国产精品porn| 久久三级视频| 欧美另类在线观看| 欧美一区二区免费| 久久婷婷综合激情| 亚洲一二三区精品| 久久精品2019中文字幕| 亚洲美女淫视频| 亚洲已满18点击进入久久| 伊人狠狠色j香婷婷综合| 亚洲人成网站影音先锋播放| 国产精品久久久一本精品| 久久这里有精品15一区二区三区| 免费欧美日韩| 午夜影视日本亚洲欧洲精品| 久久综合中文| 亚洲欧美日韩一区| 欧美成人午夜激情| 久久黄色小说| 欧美日韩另类一区| 美女国产一区| 国产精品每日更新在线播放网址| 欧美成人精品一区| 国产精品家教| 亚洲国产专区校园欧美| 国产亚洲一区二区在线观看| 最新国产の精品合集bt伙计| 国产亚洲欧美一区| 在线一区日本视频| 亚洲靠逼com| 久久精品欧美日韩精品| 亚洲欧美日韩一区在线| 欧美成人激情在线| 久久综合狠狠| 国产色视频一区| 亚洲视频每日更新| 洋洋av久久久久久久一区| 久久亚洲一区| 久久精品夜夜夜夜久久| 国产精品久久久久高潮| 亚洲精品五月天| 亚洲日本中文字幕免费在线不卡| 欧美在线观看www| 午夜欧美电影在线观看| 国产精品盗摄久久久| 亚洲日韩欧美一区二区在线| 亚洲国产激情| 久久免费视频这里只有精品| 久久久亚洲精品一区二区三区| 国产日韩欧美中文在线播放| 亚洲尤物视频网| 午夜亚洲视频| 国产精品区一区二区三区| 中国女人久久久| 亚洲一区二区精品| 国产精品欧美日韩一区| 中文欧美字幕免费| 性欧美暴力猛交69hd| 国产精品久久久久永久免费观看| 亚洲视频网在线直播| 亚洲自拍偷拍网址| 国产精品亚洲综合色区韩国| 午夜综合激情| 久久久一区二区| 亚洲第一黄色网| 欧美成人在线影院| 日韩网站在线看片你懂的| 亚洲一级片在线看| 国产免费亚洲高清| 久久久久久高潮国产精品视| 亚洲福利视频专区| 一区二区激情| 国产毛片精品视频| 久久婷婷国产综合尤物精品| 亚洲国产日韩在线| 亚洲欧美日韩国产一区| 国产一区二区三区高清播放| 久久久久99精品国产片| 亚洲第一中文字幕| 亚洲欧美日韩一区二区在线| 国内伊人久久久久久网站视频| 久久综合给合| 99国产一区| 久久久噜噜噜久久中文字免| 亚洲人成在线观看| 国产精品久久久久国产精品日日| 久久激情网站| 亚洲精品一区在线| 久久aⅴ国产欧美74aaa| 亚洲美女毛片| 国产专区综合网| 欧美日韩国产精品自在自线| 欧美一区午夜精品| 亚洲精品免费在线| 久色婷婷小香蕉久久| 亚洲调教视频在线观看| 国产一区观看| 欧美三级免费| 男人天堂欧美日韩| 欧美在线一二三| 在线视频日韩| 欧美电影在线观看| 久久精品首页| 国产精品99久久久久久久vr| 亚洲国产精品久久久久婷婷884| 国产精品人成在线观看免费| 欧美国产视频一区二区| 欧美一区二区精品| 一区二区三区免费看| 亚洲激情视频网| 欧美暴力喷水在线| 香蕉乱码成人久久天堂爱免费| 最新国产成人av网站网址麻豆| 国产一区二区三区久久悠悠色av| 欧美午夜精品电影| 欧美精品二区| 美日韩在线观看| 久久精品国产亚洲5555| 亚洲欧美日韩国产精品| 日韩视频在线播放| 亚洲精品视频免费| 亚洲经典在线| 亚洲国产精品传媒在线观看| 麻豆精品91| 久久这里有精品视频| 久久精品一区二区国产| 欧美影院精品一区| 午夜一区不卡| 欧美一区三区三区高中清蜜桃 | 欧美日韩成人综合在线一区二区| 免费在线观看日韩欧美| 久久男人av资源网站| 久久精品成人| 久久资源av| 欧美成人在线免费观看| 欧美精品一卡| 欧美激情亚洲自拍| 欧美精品九九| 欧美日韩亚洲三区| 欧美日韩综合视频| 国产精品久久九九| 国产伦精品一区二区三区在线观看 | 老司机凹凸av亚洲导航| 麻豆精品一区二区综合av| 欧美α欧美αv大片| 亚洲国产cao| 亚洲精品看片| 亚洲男女自偷自拍| 久久久999| 欧美精品乱码久久久久久按摩| 欧美日韩精品久久久| 欧美日韩在线高清| 国产欧美一区二区三区另类精品| 国产午夜精品全部视频播放| 怡红院精品视频在线观看极品| 亚洲国产精品视频| 一区二区三区欧美亚洲| 小辣椒精品导航| 欧美1级日本1级| 亚洲欧洲日产国产网站| 亚洲一区二区视频| 久久久久这里只有精品| 欧美日韩国产精品成人| 国产日韩欧美日韩| 91久久久亚洲精品| 亚洲欧美三级在线| 久热国产精品视频| 亚洲精品在线视频| 欧美亚洲一区二区在线| 欧美激情网友自拍| 国产亚洲精品v| 日韩午夜激情电影| 久久精品卡一| 99亚洲一区二区| 久久午夜av| 国产精品一区二区黑丝| 亚洲欧洲另类国产综合| 欧美一区=区| 亚洲精品久久久蜜桃| 久久精品av麻豆的观看方式 | 性欧美大战久久久久久久久| 欧美国产亚洲精品久久久8v| 国产欧美视频一区二区三区| 亚洲蜜桃精久久久久久久| 久久婷婷久久一区二区三区| 夜夜夜久久久| 男人的天堂亚洲| 国模私拍视频一区| 亚洲在线免费观看| 亚洲激情在线播放| 久久久欧美精品sm网站| 国产欧美精品一区二区三区介绍| 99成人在线| 91久久中文|