• <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>

            longshanks

              C++博客 :: 首頁 :: 聯系 :: 聚合  :: 管理
              14 Posts :: 0 Stories :: 214 Comments :: 0 Trackbacks

            常用鏈接

            留言簿(10)

            我參與的團隊

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            GP技術的展望——先有鴻鈞后有天

            莫華楓


                自從高級語言出現以來,類型始終是語言的核心。幾乎所有語言特性都要以類型作為先決條件。類型猶如天地,先于萬物而存在。但是,是否還有什么東西比類型更加原始,更加本質,而先于它存在呢?請往下看。:)

            泛型和類型

                泛型最簡短最直觀的描述恐怕應該是:the class of type。盡管這樣的描述不算最完備,但也足以說明問題。早在60年代,泛型的概念便已經出現。最初以“參數化類型”的名義存在。70年代末期發展起來的 恐龍級的Ada(我的意思不是說Augusta Ada Byron Lovelace伯爵夫人是恐龍,從畫像上看,這位程序員的祖師奶長得相當漂亮:)),尚未擁有oop(Ada83),便已經實現了泛型(Generic)。盡管泛型歷史悠久,但真正全面地發展起來,還是在90年代初, 天才的Alexander A. Stepanov創建了stl,促使了“泛型編程”(Generic Programming)的確立。
                出于簡便的目的,我套用一個老掉牙的“通用容器”來解釋泛型的概念。(就算我敷衍吧:P,畢竟重頭戲在后面,具體的請看前面給出的鏈接)。假設我在編程時需要一個int類型的棧,于是我做了一個類實現這個棧:
                class IntStack {...};
                用的很好。過了兩天,我又需要一個棧,但是類型變成了double。于是,我再另寫一個:
                class DoubleStack {...};
                不得了,好象是通了馬蜂窩,不斷地出現了各種類型的棧的需求,有string的,有datetime的,有point的,甚至還有一個Dialog的。每 種類型都得寫一個類,而且每次代碼幾乎一樣,只是所涉及的類型不同而已。于是,我就熱切地期望出現一種東西,它只是一個代碼的框架,實現了stack的所 有功能,只是把類型空著。等哪天我需要了,把新的類型填進去,便得到一個新的stack類。
                這便是泛型。
                但是,僅僅這些,還不足以成就GP的威名。
                我有一個古怪的需求(呵呵,繼續敷衍。:)):
                做一個模板,內部有一個vector<>成員:
                template<typename T> A
                {
                    ...
                    vector<T> m_x;
                };
                可是,如果類型實參是int類型的話,就得用set<>。為了使用的方便,模板名還得是A。于是,我們就得使用下面的技巧:
                template<> A<int>
                {
                    ...
                    set<T> m_x;
                };
                這叫特化(specialization),相當于告訴編譯器如果類型實參是int,用后面那個。否則,用前面的。特化實際上就是根據類型實參由編譯器執行模板的選擇。換句話說,特化是一種編譯期分派技術。
                這里還有另一個更古怪需求:如果類型實參是指針的話,就用list<>。這就得用到另一種特化了:
                template<typename T> A<T*>
                {
                    ...
                    list<T> m_x;
                }
                這是局部特化(partial specialization),而前面的那種叫做顯式特化(explicit specialization),也叫全特化。局部特化則是根據類型實參的特征(或者分類)執行的模板選擇。
                最后,還有一個最古怪的需求:如果類型實參擁有形如void func(int a)成員函數的類型,那么就使用deque。這個...,有點難。現有的C++編譯器,是無法滿足這個要求的。不過希望還是有的,在未來的新版C++09中,我們便可以解決這個問題。

            Concept和類型

                concept是GP發展必然結果。正如前面所提到的需求,我們有時候會需要編譯器能夠鑒識出類型的某些特征,比如擁有特定的成員等等,然后執行某種操作。下面是一個最常用的例子:
                swap()是一個非常有用的函數模板,它可以交換兩個對象的內容,這是swap手法的基礎。swap()的基本定義差不多是這樣:
                template<typename T> swap(T& lhs, T& rhs) {
                    T tmp(lhs);
                    lhs=rhs;
                    rhs=tmp;
                }
                但是,如果需要交換的對象是容器之類的大型對象,那么這個swap()的性能會很差。因為它執行了三次復制,這往往是O(n)的。標準容器都提供了一個 swap成員函數,通過交換容器內指向數據緩沖的指針,獲得O(1)的性能。因此,swap()成員是首選使用的。但是,這就需要程序員識別對象是否存在 swap成員,然后加以調用。如果swap()函數能夠自動識別對象是否存在swap成員,那么就可以方便很多。如果有swap成員,就調用成員,否則, 就是用上述通過中間變量交換的版本。
                這就需要用到concept技術了:
                template<Swappable T> void swap(T& lhs, T& rhs) {
                    lhs.swap(rhs);
                }
                這里,Swappable是一個concept:
                concept Swappable<typename T> {
                    void T::swap(T&);
                }
                于是,如果遇到擁有swap成員函數的對象,正好符合Swappable concept,編譯器可以使用第二個版本,在O(1)復雜度內完成交換。否則,便使用前一個版本:
                vector a, b;
                ... //初始化a和b
                swap(a,b); //使用后一個版本
                int c=10, d=23;
                swap(c, d); //使用前一個版本
                這里的swap()也是運用了特化,所不同的是在concept的指導下進行的。這樣的特化有時也被稱作concept based overload。
                從上面的例子中可以看到,原先的特化,無論是全特化,還是局部特化,要么特化一個類型,要么特化一個大類(如指針)的類型。但無法做到更加精細。比如,我 希望一個模板能夠針對所有的整數(int,long,short,char等)進行特化,這在原先是無法做到的。但擁有了concept之后,我們便可以 定義一個代表所有整數的concept,然后使用這個整數concept執行特化。換句話說,concept使得特化更加精細了,整個泛型系統從原來“離 散”的變成了“連續”的。
                不過上面那個concept特化的模板看起來實在不算好看,頭上那一坨template...實在有礙觀瞻。既然是concept based overload,那么何不直接使用重載的形式,而不必再帶上累贅的template<...>:
                void fun(anytype a){...} //#1,anytype是偽造的關鍵字,表示所有類型。這東西最好少用。
                void fun(Integers a){...} //#2,Integers是concept,表示所有整型類型
                void fun(Floats a){...} //#3,Floats是concept,表示所有浮點類型
                void fun(long a){...} //#4
                void fun(int a){...} //#5
                void fun(double a){...} //#6
                ...
                int x=1;
                long y=10;
                short z=7;
                string s="aaa";
                float t=23.4;
                fun(x); //選擇#5
                fun(y); //選擇#4
                fun(z); //選擇#2
                fun(s); //選擇#1
                fun(t); //選擇#3
                這種形式在語義上與原來的模板形式幾乎一樣。注意,是幾乎。如下的情形是重載形式無法做到的:
                template<Integers T> T swap(T lhs, T rhs) {
                    T temp(lhs);
                    ...
                }
                這里,模板做到了兩件事:其一,模板萃取出類型T,在函數體中,可以使用T執行一些操作,比如上述代碼中的臨時對象temp的構造。這個問題容易解決,因為萃取類型T還有其他的方法,一個typeof()操作符便可實現:
                Integers swap(Integers lhs, Integers rhs) {
                    typeof(lhs) temp(lhs);
                    ...
                }
                其二,模板保證了lhs,rhs和返回值都是同一類型。這個問題,可以通過施加在函數上的concept約束解決:
                Integers swap(Integers lhs, Integers rhs)
                    requires SameType<lhs, rhs>
                        && SameType<lhs, retval> {  //retval是杜撰的關鍵字,用以表示返回值
                    typeof(lhs) temp(lhs);
                    ...
                }
                相比之下,重載形式比較繁瑣。總體而言,盡管重載形式冗長一些,但含義更加明確,更加直觀。并且在concept的接口功能作用下,對參數類型一致的要求 通常并不多見(一般在基本類型,如整型等,的運算處理中較多見。因為這些操作要求類型有特定的長度,以免溢出。其他類型,特別是用戶定義類型,通常由于封 裝的作用,不會對類型的內部特性有過多要求,否則就不應使用泛型算法)。如果可以改變語法的話,那么就能用諸如@代替typeof,==代替 SameType的方法減少代碼量:
                Integers swap(Integers lhs, Integers rhs)
                    requires @lhs == @rhs && @lhs == @retval {
                    @lhs temp(lhs);
                    ...
                }
               

            Concept、類型和對象

                事情還可以有更加夸張的發展。前面對泛型進行了特化,能不能對類型也來一番“特化”呢?當然可以:
                void fun(int a);
                void fun(int a:a==0); //對于類型int而言,a==0便是“特化”了
                更完整的,也可以有“局部特化”:
                void fun(int a); //#1
                void fun(int a:a==0); //#2
                void fun(int a:a>200); //#3
                void fun(int a:a<20&&a>10); //#4
                void fun(int a:(a>70&&a<90)||(a<-10)); //#5
                ...
                int a=0, b=15, c=250, d=-50;
                fun(80); //使用#5
                fun(50); //使用#1
                fun(a); //使用#2
                fun(b); //使用#4
                fun(c); //使用#3
                fun(d); //使用#5
                實際上,這無非是在參數聲明之后加上一組約束條件,用以表明該版本函數的選擇條件。沒有約束的函數版本在沒有任何約束條件匹配的情況下被選擇。對于使用立 即數或者靜態對象的調用而言,函數的選擇在編譯期執行,編譯器根據條件直接調用匹配的版本。對于變量作為實參的調用而言,則需要展開,編譯器將自動生成如 下代碼:
                //首先將函數重新命名,賦予唯一的名稱
                void fun_1(int a); //#1
                void fun_2(int a); //#2
                void fun_3(int a); //#3
                void fun_4(int a); //#4
                void fun_5(int a); //#5
                //然后構造分派函數
                void fun_d(int a) {
                    if(a==0)
                        fun_2(a);
                    else if(a>200)
                        fun_3(a);
                    ...
                    else
                        fun_1(a);
                }
                在某些情況下,可能需要對一個對象的成員做出約束,此時便可以采用這種形式:
                struct A
                {
                    float x;
                };
                ...
                void fun(A a:a.x>39.7);
                ...
                這種施加在類型上的所謂“特化”實際上只是一種語法糖,只是由編譯器自動生成了分派函數而已。這個機制在Haskell等語言中早已存在,并且在使用上帶 來很大的靈活性。如果沒有這種機制,那么一旦需要增加函數分派條件,那么必須手工修改分派函數。如果這些函數,包括分派函數,是第三方提供的代碼,那么修 改將是很麻煩的事。而一旦擁有了這種機制,那么只需添加一個相應的函數重載即可。
                當concept-類型重載和類型-對象重載混合在一起時,便體現更大的作用:
                void fun(anytype a);
                void fun(Integers a);
                void fun(Floats a);
                void fun(long a);
                void fun(int a);
                void fun(double a);
                void fun(double a:a==0.8);
                void fun(short a:a<10);
                void fun(string a:a=="abc");
                ...
                concept-類型-對象重載體系遵循一個原則:優先選擇匹配的函數中最特化的。這實際上是類型重載規則的擴展。大的來說,所有類型比所屬的 concept更加特化,所有對象約束比所屬的類型更加特化。對于concept而言,如果concept A refine自concept B,那么A比B更加特化。同樣,如果一個類型的約束強于另一個,那么前一個就比后一個更加特化,比如a==20比a>10更加特化。綜合起來,可以 有這樣一個抽象的規則:兩個約束(concept,或者施加在對象上的約束)A和B,作用在類型或者對象上分別產生集合,如果A產生的集合是B產生的集合 的真子集,那么便認為A比B更加特化。
                根據這些規則,實際上可以對一個函數的重載構造出一個“特化樹”:

                越接近樹的根部,越泛化,越接近葉子,越特化。調用時使用的實參便在這棵“特化樹”上搜索,找到最匹配的函數版本。
                concept-類型-對象體系將泛型、類型和對象統一在一個系統中,使得函數的重載(特化)具有更簡單的形式和規則。并且,這個體系同樣可以很好地在類模板上使用,簡化模板的定義和使用。

            類模板

                C++的類模板特化形式并不惹人喜愛:
                template<typename T> A{...}; //基礎模板
                template<> A<int>{...}; //顯式特化(全特化)
                template<typename T> A<T*>{...}; //局部特化
                在C++09中,可以直接用concept定義模板的類型形參:
                template<Integers T> A{...};
                實質上,這種形式本身就是一種局部特化,因而原本那種累贅局部特化形式可以廢除,代之以concept風格的形式:
                template<Pointer T> A{...}; //Pointer表示此處采用指針特化模板
                同樣,如果推廣到全特化,形式也就進一步簡單了:
                template<int> A{...}; //這個形式有些突兀,這里只打算表達這個意思,應該有更“和諧”的形式
                如果模板參數是對象,則使用現有的定義形式:
                template<int a> A{...};
                更進一步,可以引入對象的約束:
                template<int a:a>10> A{...};
                此外,C++中在模板特化之前需要有基礎模板。但實際上這是多余的,D語言已經取消了這個限制,這對于簡化模板的使用有著莫大的幫助。

            從本質上講...

                從本質上講,我們可以把所有類型看作一個集合T={ti},而concept則是施加在類型集合上的約束。通過concept這個約束,我們便可以獲得類 型集合T的一個子集C。理論上,所有concept所對應的類型子集Cj構成了類型集合的冪集{Cj}。在{Cj}中,有兩類類型子集是很特殊的。一組是 T本 身,即所有類型。存在一個concept不對T施加任何約束,便得到了C0=T。第二類則是另一個極端,存在一組concept,施加在T上之后所得的類 型子集僅包含一個類型:Ci={ti}。由于這組concept與類型存在一一對應的關系,那么我們便可以用這組concept來指代類型。也就是把類型 作為特殊的concept處理。如此,concept便同類型統一在一個體系中。這種處理可以使我們獲得極大的好處。
                這組特殊的concept仍舊使用對應的類型名作為稱謂,仍舊稱之為“類型”,但其本質上還是concept。任何一個類型,一旦創建,也就創建了相應的特殊concept。如果在模板特化中使用一個類型的時候,實際上就是在使用相對應的那個特殊concept:
                void func(typeA a); //盡管使用了類型名typeA,但實際上這里所指的是typeA所對應的那個特殊concept。
                在這個concept體系的作用下,函數模板的特化和重載整個地統一起來(concept based overload)。
                至于作用在類型上的那種“特化”,也是同樣的道理。對于一個類型T而言,它所有的對象構成一個集合O。如果存在一組約束作用于O,那么每 一個約束對應著O的一個子集。理論上,我們可以構造出一組約束,使得他們同O的每一個子集一一對應。同樣,這些子集中有兩類子集比較特殊。一類是所有對象 的集合。另一類便是只有一個對象的子集。于是,我們可以使用這組特殊對象子集所對應的約束指代相應的對象。也就是將對象看作特殊的約束。如此,類型和對象 也被統一在一個系統中了。
                進而,類型在邏輯上被作為特殊concept處理,對象則被作為特殊的類型處理。于是,這三者便可以統一在一個體系下,一同參與特化。

            總結

                盡管形式不能代表本質,但形式的變化往往會帶來很多有益的進步。更重要的是,很多本質上的變化總會伴隨著形式上的改變。通過將concept、類型和對象 在邏輯上整合到統一的體系之中,便可以促使模板、特化、函數重載等機制在形式上達成統一。從而能夠簡化這些功能的使用。這也是當前重視語言(工具)易用性 的潮流的一個必然訴求。這個形式上的統一并非語法糖之類的表面變化。而是完全依賴于concept這個新型的類型描述(泛型)系統的確立和發展。 concept的出現,彌補了以往泛型的不足,找回了泛型系統缺失的環節,彌補了泛型同類型之間的裂痕。在此作用下,便可以構建起concept-類型- 對象的抽象體系,用統一的系統囊括這三個原本分立的概念。在這個新的三位一體的系統下,使得模板的特化和重載擁有了相同的形式,進而獲得更直觀的語義,和 更好的易用性。
            posted on 2008-07-26 19:44 longshanks 閱讀(1916) 評論(10)  編輯 收藏 引用

            Feedback

            # re: GP技術的展望——先有鴻鈞后有天 2008-07-26 21:25 oldrev
            這樣的話程序中會不會出現無數只為約束函數參數而沒其他作用的 concepts,重走 C#/Java 中 interface 的老路?  回復  更多評論
              

            # re: GP技術的展望——先有鴻鈞后有天 2008-07-26 22:28 clear
            c++0x 的concept不需要顯式聲明的,比如那個Swapable,任何一個類型,只要有一個滿足其條件的swap成員函數,就自動成為這個concept的一個特例存在
            所以不會像java里面那樣對所有的類都implement一堆的interface  回復  更多評論
              

            # re: GP技術的展望——先有鴻鈞后有天 2008-07-26 22:33 Kevin Lynx
            longshanks終于又發文了。學習。  回復  更多評論
              

            # re: GP技術的展望——先有鴻鈞后有天 2008-07-26 22:36 空明流轉
            其實語法糖很重要。。。。  回復  更多評論
              

            # re: GP技術的展望——先有鴻鈞后有天 [未登錄] 2008-07-26 23:06 foxtail
            博大精深的C++ 呵呵  回復  更多評論
              

            # re: GP技術的展望——先有鴻鈞后有天 2008-07-27 00:40 bneliao
            gp要一桶漿糊了  回復  更多評論
              

            # re: GP技術的展望——先有鴻鈞后有天 2008-07-27 06:54 longshanks
            to oldrev兄:
            我不太理解“只為約束函數參數而沒其他作用的 concepts”這個概念,能否給個例子。這個問題我這么想:concept以及runtime concept同oop的interface的差異我以前的blog和toplanguage討論中都談論過。本質上,interface是“用一個類型來表征一組類型”,邏輯上就不那么和諧。而concept則是用一個獨立的,專門用于描述一組類型的概念實現這個功能。interface的弊端,比如侵入的接口、造成類型耦合等等,在concept中不會存在。運用concept,我們可以消除這部分問題。至于其他的interface問題,可能來自于需求和設計,通常也無法全部通過這種技術手段消除。
            當然就像clear兄所說,concept可以auto(需要在定義concept指定auto),這也消除了不少麻煩。只是auto需要謹慎,只有那些含有公共語義約定的concept,比如swappable、copyable等等,以及形式(成員)非常特殊,不可能有其他語義的類型與之混淆的情況下,才可以使用,否則容易引起混亂。  回復  更多評論
              

            # re: GP技術的展望——先有鴻鈞后有天 2008-07-27 10:07 oldrev
            @longshanks

            在大多數地方 concept 的用途和 interface 是差不多的,定義一個 Copyable 的 concept 和定義一個 ICopyable 是一樣的麻煩,雖然 concept 是非侵入的。
              回復  更多評論
              

            # re: GP技術的展望——先有鴻鈞后有天 2008-07-27 11:10 longshanks
            @oldrev
            這個就沒辦法了。這些接口都是需求設計驅使的,只是不同的手段實現而已。這方面的問題由于沒有廣泛的應用,還不太清楚,需要實踐檢驗。
            concept相比interface的直接好處在于兩點:1、不需要在接口實現類型產生前定義concept,任何時候都可以,這樣可以減少設計上的壓力。這是非侵入的好處。2、concept驅動下的模板在runtime和compiletime是相同的,也就是同一個模板可以同時用于runtime和static,而不需要另搞一套。
            間接的好處是會對語言整個模型產生根本性的影響,從而消除語言某些方面的復雜性和缺陷。這個我打算在下一篇blog里探討。  回復  更多評論
              

            # re: GP技術的展望——先有鴻鈞后有天 2008-07-28 15:18 oldrev
            @longshanks
            期待ing....  回復  更多評論
              

            久久精品国产99国产精品| 久久精品亚洲精品国产色婷 | 精品久久久久久无码国产| 亚洲成人精品久久| 久久久免费观成人影院| 精品国产99久久久久久麻豆| 久久精品午夜一区二区福利| 久久国产香蕉一区精品| 国产精品久久久久蜜芽| 久久99国产精品久久久| 久久综合精品国产一区二区三区| 久久久久久免费视频| 色综合久久天天综合| 欧美伊人久久大香线蕉综合| 国产亚洲欧美精品久久久| 久久精品国产WWW456C0M| 久久国产色AV免费观看| 色诱久久av| 国内精品久久久久影院免费| 亚洲欧美一区二区三区久久| 久久青青草原国产精品免费| 中文字幕乱码久久午夜| 欧美精品一区二区久久| 久久久久久综合一区中文字幕| 久久婷婷色香五月综合激情| 久久久青草久久久青草| 久久综合给合久久狠狠狠97色69| 久久综合一区二区无码| 91久久福利国产成人精品| 久久国产精品99精品国产| 久久久精品日本一区二区三区| 久久不射电影网| 精品国产VA久久久久久久冰| 久久国产欧美日韩精品免费| 一本大道加勒比久久综合| 99久久综合狠狠综合久久止| 亚洲人成精品久久久久| 久久久久久伊人高潮影院| 午夜精品久久久久久影视777| 99热精品久久只有精品| 91精品国产色综久久|