• <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++博客 :: 首頁(yè) :: 聯(lián)系 :: 聚合  :: 管理
              14 Posts :: 0 Stories :: 214 Comments :: 0 Trackbacks

            常用鏈接

            留言簿(10)

            我參與的團(tuán)隊(duì)

            搜索

            •  

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            2008年9月18日 #

            三只小豬
            莫華楓

                小時(shí)候聽說過三只小豬的故事,隱約記得故事是講三只小豬用不同方法造房子,對(duì)抗老狼。這些天做軟件,遇到一個(gè)無比簡(jiǎn)單的問題,但在三種不同的語(yǔ)言中,卻有著截然不同的解法。

                最近,冷不丁地接到公司下派的一個(gè)緊急任務(wù),做手持POS和PC程序之間交換數(shù)據(jù)的程序。各種各樣的麻煩中,有一個(gè)小得不起眼的問題,POS機(jī)中數(shù)據(jù)的字 節(jié)序和PC相反。這可不算是什么啊。沒錯(cuò),是在太簡(jiǎn)單了。盡管如此,還是引發(fā)了一場(chǎng)爭(zhēng)論。做POS程序的,希望PC程序做轉(zhuǎn)換。做PC程序的,希望POS 程序做轉(zhuǎn)換。(誰(shuí)都想少做點(diǎn),對(duì)吧;))。最終,作為和事佬的我,為了維護(hù)和諧的氛圍,攬下了這件事。當(dāng)然,到底在那里做,還是要決定的。最終選擇在PC 上,畢竟PC上調(diào)試起來容易。(天煞的,這個(gè)POS機(jī)沒有debug,也沒有模擬器,顯示屏還沒我手機(jī)的大,做起來著實(shí)費(fèi)事)。
                其實(shí),我的本意是想在POS上做這個(gè)轉(zhuǎn)換。因?yàn)镻OS用的是C(一個(gè)不知什么年代的gcc),可以直接操作字節(jié)。基本的代碼看起來差不多應(yīng)該是這樣:
                    unsigned long InvData(unsigned long val, int n) {
                        unsigned long t=val, res=0;
                        for(; n >0; n--)
                        {
                            res = res << 8;
                            res |= (unsigned char)t;
                            t = t >> 8;
                        }
                        return res;
                    }
                n是數(shù)據(jù)類型的字節(jié)長(zhǎng)度。這里使用了最長(zhǎng)的無符號(hào)整數(shù)類型。這是核心轉(zhuǎn)換函數(shù),各種類型的轉(zhuǎn)換函數(shù)都可以從中派生:
                    long InvDataLong(long val) {
                        return (long)InvData((unsigned long)val, sizeof(val));
                    }
                    short InvDataShort(short val) {
                        return (short)InvData((unsigned short)val, sizeof(val));
                    }
                    ...
                最后,有一個(gè)比較特殊的地方,float。float的編碼不同于整型,如果直接用(unsigned long)強(qiáng)制類型轉(zhuǎn)換,只會(huì)把float數(shù)值的整數(shù)部分賦予參數(shù),得不到正確的結(jié)果。正確的做法,應(yīng)當(dāng)是把float占用的四個(gè)字節(jié)直接映射成一個(gè) unsigned long:
                    float InvDataFloat(float val) {
                        float val=InvData(*(unsigned long*)(&val), sizeof(val));
                        return *(float*)(&val);
                    }
                通過將float變量的地址強(qiáng)制轉(zhuǎn)換成unsigned long*類型,然后再dereference成unsigned long類型。當(dāng)然還有其他辦法,比如memcpy,這里就不多說了。至于double類型,為了簡(jiǎn)化問題,這里將其忽略。如果有64位的整型,那么 double可以采用類似的解法。否則,就必須寫專門的處理函數(shù)。
                當(dāng)然,最終我還是使用C#寫這個(gè)轉(zhuǎn)換。相比之下,C#的轉(zhuǎn)換代碼顯得更具現(xiàn)代風(fēng)味。基本算法還是一樣:
                    public static ulong DataInv(ulong val, int n)
                    {
                        ulong v1_ = val, v2_ = 0;

                        for (; n > 0; n--)
                        {
                            v2_ <<= 8;
                            v2_ |= (byte)v1_;
                            v1_ >>= 8;
                        }

                        return v2_;
                    }
                對(duì)于習(xí)慣于C/C++的同學(xué)們注意了,long/ulong在C#中不是4字節(jié),而是8字節(jié)。也就是C/C++中的longlong。以這個(gè)函數(shù)為基礎(chǔ),其它整數(shù)類型的字節(jié)序轉(zhuǎn)換也就有了:
                    public static ulong DataInv(ulong val)
                    {
                        return DataInv(val, sizeof(ulong));
                    }

                    public static uint DataInv(uint val)
                    {
                        return (uint)DataInv((ulong)val, sizeof(uint));
                    }

                    public static int DataInv(int val)
                    {
                        return (int)DataInv((uint)val);
                    }
                    ...
                然而,面對(duì)float,出現(xiàn)了麻煩。在C#中,沒有指針,無法象C那樣將float展開成ulong。(unsafe代碼可以執(zhí)行這類操作,但這不是C#嫡親的特性,并且是違背C#設(shè)計(jì)理念的。這里不做考慮)。C#提供了另一種風(fēng)格的操作:
                    public static float DataInv(float val)
                    {
                        float res_ = 0;

                        byte[] buf_ = BitConverter.GetBytes(val);
                        byte t = 0;

                        t = buf_[0];
                        buf_[0] = buf_[3];
                        buf_[3] = t;

                        t = buf_[1];
                        buf_[1] = buf_[2];
                        buf_[2] = t;

                        res_ = BitConverter.ToSingle(buf_, 0);

                        return res_;
                    }
                這個(gè)做法盡管有些累贅,但道理上很簡(jiǎn)單:把float變量轉(zhuǎn)換成一個(gè)字節(jié)流,然后把相應(yīng)的位置對(duì)調(diào),就獲得了字節(jié)反序的float。相比C的float轉(zhuǎn) 換,C#明顯不夠簡(jiǎn)練。原因很簡(jiǎn)單,C#根本不是用來干這個(gè)的。C是一種非常底層的語(yǔ)言,它的內(nèi)存模型是完全直觀的,與硬件系統(tǒng)相對(duì)應(yīng)的。因而,對(duì)于這種 與機(jī)器相關(guān)的操作,當(dāng)然也顯得游刃有余。而C#定位于高層開發(fā)的高級(jí)語(yǔ)言,底層的內(nèi)存模型是被屏蔽的,程序員無需知道和關(guān)心。
                不過,C#的代碼卻擁有它的優(yōu)勢(shì)。只需看一眼這些函數(shù)的使用代碼,便不言自明了:
                    //C代碼
                    int x=234;
                    float y=789.89;
                    short z=332;
                    x=InvDataInt(x);
                    y=InvDataFloat(y);
                    z=InvDataShort(z);

                    //C#代碼
                    int x=234;
                    float y=789.89;
                    short z=332;
                    x=DataInv(x);
                    y=DataInv(y);
                    z=DataInv(z);
                在C代碼中,對(duì)于不同的類型,需要使用不同命名的函數(shù)。而在C#代碼中,則只需使用DataInv這樣一個(gè)函數(shù)名。至于屆時(shí)選用那個(gè)版本的函數(shù),編譯器會(huì) 根據(jù)實(shí)際的類型自動(dòng)匹配。C#運(yùn)用函數(shù)重載這個(gè)特性,使得調(diào)用代碼可以采用統(tǒng)一的形式。即便是數(shù)據(jù)的類型有所變化,也無需對(duì)調(diào)用代碼做任何修改。(這在我 的開發(fā)過程中的得到了驗(yàn)證,傳輸數(shù)據(jù)的成員類型曾經(jīng)發(fā)生變化,我也只是修改了數(shù)據(jù)結(jié)構(gòu)的定義,便將問題搞定)。這一點(diǎn),在C中是無法做到的。
                歸結(jié)起來,C由于側(cè)重于底層,在數(shù)據(jù)轉(zhuǎn)換方便的靈活性,使得轉(zhuǎn)換代碼的構(gòu)建更加容易。而C#則得益于函數(shù)重載,在轉(zhuǎn)換代碼使用方面,有獨(dú)到的優(yōu)勢(shì)。
                迄今為止,三只小豬中,還只有兩只出現(xiàn)。下面就要第三只出場(chǎng)了。
                作為C++的粉絲,我會(huì)自然而然地想到使用C++來實(shí)現(xiàn)這個(gè)轉(zhuǎn)換功能。于是便有了如下的代碼:
                   unsigned long InvData(unsigned long val, int n) {
                        unsigned long t=val, res=0;
                        for(; n >0; n--)
                        {
                            res = res << 8;
                            res |= (unsigned char)t;
                            t = t >> 8;
                        }
                    }
                    long InvData(long val) {
                        return (long)InvData((unsigned long)val, sizeof(val));
                    }
                    short InvData(short val) {
                        return (short)InvData((unsigned short)val, sizeof(val));
                    }
                    ...
                    float InvData(float val) {
                        float val=InvData(*(unsigned long*)(&val), sizeof(val));
                        return *(float*)(&val);
                    }
                這些代碼就好象是C和C#代碼的雜交后代。既有C的底層操作,也有C#的函數(shù)重載,兼有兩者的優(yōu)點(diǎn)。
                不過,還能做得更好:
                    template<typename T>
                    T InvData(T val) {
                        T t=val, res=0;
                        int n=sizeof(T);
                        for(; n >0; n--)
                        {
                            res = res << 8;
                            res |= (unsigned char)t;
                            t = t >> 8;
                        }
                        return (T)res;
                    }
                這樣,就把所有的整型都一網(wǎng)打盡了,僅用一個(gè)函數(shù)模板,便完成了原先諸多函數(shù)所做的工作。而float版本的函數(shù)則保持不變,作為InvData()的一個(gè)重載。按照C++的函數(shù)模板-重載規(guī)則,float版的函數(shù)重載將被優(yōu)先使用。

                好了,三只小豬的故事講完了。前兩只小豬各有優(yōu)點(diǎn),也各有缺點(diǎn)。而第三只小豬則雜合和前兩者的優(yōu)點(diǎn),并且具有更大的進(jìn)步。盡管第三只小豬存在各種各樣的缺陷,但畢竟它的眾多特性為我們帶來了很多效率和方便,這些還是應(yīng)當(dāng)值得肯定的。

            附:第三只小豬的其他手段:
            1、強(qiáng)制轉(zhuǎn)換成字符串?dāng)?shù)組
            template<typename T>
            T InvData1(T v) {
                unsigned char* pVal1=(unsigned char*)(&v)
                    , *pVal2=pVal1+sizeof(T)-1, t;
                while(pVal2-pVal1>1)
                {
                    t=*pVal2;
                    *pVal2=*pVal1;
                    *pVal1=t;
                    pVal1++;
                    pVal2--;
                }
                return v;
            }
            2、使用標(biāo)準(zhǔn)庫(kù),blog上有人留言建議的
            template<typename T>
            T InvData(T v) {
                unsigned char* pVal=(unsigned char*)(&v);
                size_t n=sizeof(T);
                std::reverse(pVal, pVal+n, pVal);
            }
            3、使用traits
            template<size_t n> struct SameSizeInt;
            template<> struct SameSizeInt<1> { typedef unsigned char Type; };
            template<> struct SameSizeInt<2> { typedef unsigned short Type; };
            template<> struct SameSizeInt<4> { typedef unsigned long Type; };
            template<> struct SameSizeInt<8> { typedef unsigned longlong Type; };

            template<typename T>
            T InvData(T v) {
                size_t n=sizeof(T);
                typedef SameSizeInt<sizeof(T)>::Type NewT;
                NewT v1=*(NewT*)(&v), v2=0;
                for(; n >0; n--)
                {
                    v2= v2<< 8;
                    v2|= (unsigned char)v1;
                    v1 = v1 >> 8;
                }
                return *(T*)(&v2);
            }

            甚至可以使用tmp去掉其中的循環(huán)。在C++中,這類任務(wù)的實(shí)現(xiàn)方法,完全看程序員的想象力了。:)
            posted @ 2008-09-18 19:25 longshanks 閱讀(1935) | 評(píng)論 (3)編輯 收藏

            2008年8月2日 #

            GP技術(shù)的展望——C--
            莫華楓

                C++的復(fù)雜是公認(rèn)的,盡管我認(rèn)為在人類的聰明智慧之下,這點(diǎn)復(fù)雜壓根兒算不上什么。不過我得承認(rèn),對(duì)于一般的應(yīng)用而言,C++對(duì)程序員產(chǎn)生的壓力還是不 小的。畢竟現(xiàn)在有更多更合適的選擇。僅僅承認(rèn)復(fù)雜,這沒有什么意義。我不時(shí)地產(chǎn)生一個(gè)念頭:有什么辦法既保留C++的優(yōu)點(diǎn),而消除它的缺點(diǎn)和復(fù)雜。我知道 D語(yǔ)言在做這樣的事情。但是,D更多地是在就事論事地消除C++的缺陷,而沒有在根本上消除缺陷和復(fù)雜性。
                一般而言,一樣?xùn)|西復(fù)雜了,基本上都是因?yàn)闁|西太多。很顯然,C++的語(yǔ)言特性在眾多語(yǔ)言中是數(shù)一數(shù)二的。于是,我便想到或許把C++變成“C--”,可以治好C++的復(fù)雜之病。在探討這個(gè)問題之前,讓我們先從C出發(fā),看看C++為何如此復(fù)雜。

            C和C++

                盡管存在這樣那樣的不足,比如non-lalr的語(yǔ)法、隱式的指針類型轉(zhuǎn)換等,但C語(yǔ)言的設(shè)計(jì)哲學(xué)卻是足夠經(jīng)典的。C語(yǔ)言有一個(gè)非正式的分類,認(rèn)為它既非匯編這樣的低級(jí)語(yǔ)言,也非Pascal那樣的高級(jí)語(yǔ)言, 而應(yīng)該算作中級(jí)語(yǔ)言,介于其他兩類語(yǔ)言之間。這種分類恰如其分地點(diǎn)出了C語(yǔ)言的特點(diǎn)和理念:以高級(jí)語(yǔ)言語(yǔ)法形式承載了低級(jí)語(yǔ)言的編程模型。低級(jí)語(yǔ)言的特點(diǎn) 是可以直接地描述硬件系統(tǒng)的結(jié)構(gòu)。C則繼承了這個(gè)特點(diǎn)。C語(yǔ)言直觀地反映了硬件的邏輯構(gòu)造,比如數(shù)組是內(nèi)存塊,可以等價(jià)于指針。在C語(yǔ)言中,我們可以幾乎 直接看到硬件的構(gòu)造,并且加以操作。這些特性對(duì)于底層開發(fā)至關(guān)重要。
                然而,C的這種直觀簡(jiǎn)潔的模型過于底層和瑣碎,不利于應(yīng)用在那些構(gòu)造復(fù)雜、變化多樣的應(yīng)用中。針對(duì)C的這些弱點(diǎn),Bjarne Stroustrup決心利用OOP技術(shù)對(duì)C語(yǔ)言進(jìn)行改造,從而促使了C++的誕生。C++全面(幾乎100%)地兼容C,試圖以此在不損失C語(yǔ)言的直觀 和簡(jiǎn)潔的情況下,同時(shí)具備更強(qiáng)的軟件工程特性,使其具備開發(fā)大型復(fù)雜系統(tǒng)的優(yōu)勢(shì)。這個(gè)目標(biāo)“幾乎”達(dá)到了,但是代價(jià)頗為可觀。
                在經(jīng)歷了80、90年代的輝煌之后,C++的應(yīng)用領(lǐng)域開始退步。一方面,在底層應(yīng)用方面,C++的很多特性被認(rèn)為是多余的。如果不使用這些特性,那么 C++則同C沒有什么差別。相反這些特性的使用,對(duì)開發(fā)團(tuán)隊(duì)的整體能力提出了更高的要求。因而,在最底層,很多人放棄了C++而回歸了C,因?yàn)槟切└呒?jí)特 性并未帶來很多幫助,反而產(chǎn)生了很多負(fù)擔(dān)。另一方面,在高層開發(fā)中,業(yè)務(wù)邏輯和界面也無需那么多底層的特性和苛刻的性能要求,更多簡(jiǎn)單方便、上手容易的語(yǔ) 言相比C++更加適合。C++的應(yīng)用被壓縮在中間層,隨著業(yè)界系統(tǒng)級(jí)開發(fā)的不斷專業(yè)化,C++的使用規(guī)模也會(huì)越來越小。(當(dāng)然,它所開發(fā)的應(yīng)用往往都是關(guān) 鍵性的,并且是沒有選擇余地的)。實(shí)際上,C++在這個(gè)層面也并非完美的工具。目前無法取代是因?yàn)闆]有相同級(jí)別的替代品。D或許是個(gè)強(qiáng)有力的競(jìng)爭(zhēng)者,但一 方面出于歷史遺留代碼的規(guī)模和應(yīng)用慣性,另一方面D也并未完全解決C++面臨的復(fù)雜性問題,D也很難在可見的將來取代C++。
                實(shí)際上,C++的這種尷尬地位有著更深層次的原因。C++的本意是在保留C的底層特性基礎(chǔ)上,增加更好的軟件工程特性。但是,C++事實(shí)上并未真正意義上 地保留C的底層特性。回顧一下C的設(shè)計(jì)理念——直觀而簡(jiǎn)潔地反映底層硬件的特性。C++通過兼容C獲得了這種能力。但是這里有個(gè)問題,如果我要獲得C的這 種簡(jiǎn)單直觀性,就必須放棄C++中的很多高級(jí)特性。這里最明顯的一個(gè)例子便是pod(plain old data)。
                在C中壓根沒有pod的概念,因?yàn)樗械膶?duì)象都是pod。但是,C++中有了pod。因?yàn)镃++的對(duì)象可能不是一個(gè)pod,那么我們便無法象在C當(dāng)中那樣 獲得直觀簡(jiǎn)潔的內(nèi)存模型。對(duì)于pod,我們可以通過對(duì)象的基地址和數(shù)據(jù)成員的偏移量獲得數(shù)據(jù)成員的地址,或者反過來。但在非pod對(duì)象中,卻無法這么做。 因?yàn)镃++的標(biāo)準(zhǔn)并未對(duì)非pod對(duì)象的內(nèi)存布局作出定義,因而對(duì)于不同的編譯器,對(duì)象布局是不同的。而在C中,僅僅會(huì)因?yàn)榈讓佑布到y(tǒng)的差異而影響對(duì)象布 局。
                這個(gè)問題通常并不顯而易見。但在很多情況下為我們制造了不小的障礙。比如,對(duì)象的序列化:我們?cè)噲D將一個(gè)對(duì)象以二進(jìn)制流的形式保存在磁盤中、數(shù)據(jù)庫(kù)中,或 者在網(wǎng)上傳輸,如果是pod,則直接將對(duì)象從基地址開始,按對(duì)象的大小復(fù)制出來,或傳輸,或存儲(chǔ),非常方便。但如果是非pod,由于對(duì)象的不同部分可能存 在于不同的地方,因而無法直接復(fù)制,只能通過手工加入序列化操作代碼,侵入式地讀取對(duì)象數(shù)據(jù)。(這個(gè)問題不僅僅存在于C++,其他語(yǔ)言,如java、C# 等都存在。只是它們沒有很強(qiáng)烈的性能要求,可以使用諸如reflect等手段加以處理)。同樣的問題也存在于諸如hash值的計(jì)算等方面。這對(duì)很多開發(fā)工 作造成不小的影響,不僅僅在底層,也包括很多高層的應(yīng)用。
                究其原因,C++僅僅試圖通過機(jī)械地將C的底層特性和OOP等高層特性混合在一起,意圖達(dá)到兩方兼顧的目的。但是,事與愿違,OOP的 引入實(shí)際上使得C的編程模型和其他更高級(jí)的抽象模型無法兼容。在使用C++的過程中,要么只使用C的特性,而無法獲得代碼抽象和安全性方面的好處,要么放 棄C的直觀簡(jiǎn)潔,而獲得高層次的抽象能力。反而,由于C和OOP編程模型之間的矛盾,大大增加了語(yǔ)言的復(fù)雜性和缺陷數(shù)。

            舍棄

                但是,我們可以看到在C++中,并非所有的高級(jí)特性都與C的底層特性相沖突。很多使用C而不喜歡C++的人都表示過他們?cè)饨邮?a title="OB" id="tl28">OB,也就是僅僅使用封裝 。對(duì)于RAII,基本上也持肯定的態(tài)度。或許也會(huì)接受繼承,但也表露出對(duì)這種技術(shù)帶來的復(fù)雜性的擔(dān)心。動(dòng)多態(tài)是明顯受到排斥的技術(shù)。顯然這是因?yàn)閯?dòng)多態(tài)破壞了C的編程模型,使得很多本來簡(jiǎn)單的問題復(fù)雜化。不是它不好,或者沒用,是它打破了太多的東西。
                因而,我們?cè)O(shè)想一下,如果我們?nèi)コ齽?dòng)多態(tài)特性,那么是否會(huì)消除這類問題呢?我們一步步看。
                動(dòng)多態(tài)的一個(gè)基本支撐技術(shù)是虛函數(shù)。在使用虛函數(shù)的情況下,類的每一次繼承都會(huì)產(chǎn)生一個(gè)虛函數(shù)表(vtable),其中存放的是指向虛函數(shù)的指針。這些虛函數(shù)表必須存放在對(duì)象體中,也就是和對(duì)象的數(shù)據(jù)存放在一起(至少要關(guān)聯(lián)在一起)。因而,對(duì)象在內(nèi)存里并不是以連續(xù)的方式存放,而被分割成不同的部分,甚至身首異處(詳見《Inside C++ Object Model》)。這便造成了前面所說的非pod麻煩。一旦放棄虛函數(shù)和vtable,對(duì)象的內(nèi)存布局中,便不會(huì)有東西將對(duì)象分割開。所有的對(duì)象的數(shù)據(jù)存儲(chǔ)都是連續(xù)的,因而都是pod。在這一點(diǎn)上,通過去除vtable,使得語(yǔ)言回歸了C的直觀和簡(jiǎn)單。
                動(dòng)多態(tài)的內(nèi)容當(dāng)然不僅僅是一個(gè)虛函數(shù),另一個(gè)重要的基石是繼承。當(dāng)然,我們并不打算放棄繼承,因?yàn)樗⒉恢苯悠茐腃的直觀性和簡(jiǎn)潔性。不同于虛函數(shù),繼承 不是完全為了動(dòng)多態(tài)而生的。繼承最初的用途在于代碼復(fù)用。當(dāng)它被賦予了多態(tài)含義后,才會(huì)成為動(dòng)多態(tài)的基礎(chǔ)。以下的代碼可以有兩種不同的解讀:
                class B : public A {};
                從代碼復(fù)用的角度來看,B繼承自A,表示我打算讓B復(fù)用A的所有代碼,并且增加其他功能。而從多態(tài)的角度來看,B是一個(gè)A的擴(kuò)展,B和A之間存在is-a的 關(guān)系。(B是一個(gè)A)。兩者是站在不同層面看待同一個(gè)問題。代碼復(fù)用,代表了編碼的觀點(diǎn),而多態(tài),則代表了業(yè)務(wù)邏輯的觀點(diǎn)。但是,兩者并非實(shí)質(zhì)上的一回 事。在很多情況下,基類往往作為繼承類的某種代表,或者接口,這在編碼角度來看并沒有對(duì)等的理解。而這種接口作用,則是動(dòng)多態(tài)的基礎(chǔ)。動(dòng)多態(tài)通過不同的類 繼承自同一個(gè)基類,使它們擁有共同的接口,從而可以使用統(tǒng)一的形式加以操作。作為一個(gè)極端,interface(或者說抽象基類),僅僅擁有接口函數(shù)(即vtable)而不包含任何數(shù)據(jù)成員。這是純粹的接口。
                然而,這里存在一個(gè)缺陷。一個(gè)接口所代表的是一組類,它將成為這一組類同外界交互的共同界面。但是,使用基類、或者抽象基類作為接口,實(shí)質(zhì)上是在使用一個(gè) 類型來代表一組類型。打個(gè)比方,一群人湊在一起出去旅游,我們稱他們這群人為“旅行團(tuán)”。我們知道旅行團(tuán)不是一個(gè)人,而是一個(gè)不同于“人”的概念。動(dòng)多態(tài) 里的接口相當(dāng)于把一個(gè)旅行團(tuán)當(dāng)作一個(gè)人來看待。盡管這只是邏輯上的,或許一個(gè)旅行團(tuán)的很多行為和一個(gè)人頗為相似。但是根本上而言,兩者畢竟不是相同層次的 概念。這樣的處理方法往往會(huì)帶來了很多弊端。
                為了使繼承被賦予的這重作用發(fā)揮作用,還需要一項(xiàng)非常關(guān)鍵的處理:類型轉(zhuǎn)換。請(qǐng)看以下代碼:
                void func(A* a);
                B b;
                func(&b);
                最后這行代碼施行了動(dòng)多態(tài),如果B override了A的虛函數(shù)的話。很顯然,如果嚴(yán)格地從強(qiáng)類型角度而言,&b是不應(yīng)當(dāng)作為func的實(shí)參,因?yàn)閮烧哳愋筒黄ヅ洹5侨绻芙^接 受&b作為實(shí)參,那么動(dòng)多態(tài)將無法進(jìn)行下去。因此,我們放寬了類型轉(zhuǎn)換的限制:允許繼承類對(duì)象的引用或指針隱式地轉(zhuǎn)換成基類的引用或指針。這樣, 形如func(&b);便可以順理成章地成為合法的代碼。
                然而,這也是有代價(jià)的:
                B ba[5];
                func(ba);
                后面這行函數(shù)調(diào)用實(shí)際上是一個(gè)極其危險(xiǎn)的錯(cuò)誤。假設(shè)在func()中,將形參a作為一個(gè)類型A的數(shù)組對(duì)待,那么當(dāng)我們使用ba作為實(shí)參調(diào)用func()的 時(shí)候,會(huì)將ba作為A的 數(shù)組處理。我們知道,數(shù)組內(nèi)部元素是緊挨著的,第二個(gè)元素的位置是第一個(gè)元素的基址加上元素的尺寸,以此類推。如果傳遞進(jìn)來的對(duì)象數(shù)組是B類型的,而被作 為A類型處理,那么兩者的元素位置將可能不同步。盡管B繼承自A,但是B的尺寸很有可能大于A,那么從第二個(gè)元素起,a[1]的地址并非ba[1]的地 址。于是,當(dāng)我們以a[1]訪問ba時(shí),實(shí)際上很可能在ba[0]的內(nèi)部某個(gè)位置讀取,而func()的代碼還以為是在操作ba[1]。這便是C++中的 一個(gè)重要的陷阱——對(duì)象切割。這種錯(cuò)誤相當(dāng)隱蔽,危險(xiǎn)性極大。
                由于C++試圖保留C的編程模型,因而保留了指針-數(shù)組的等價(jià)性。這種等價(jià)性體現(xiàn)了數(shù)組的本質(zhì)。這在C中是一項(xiàng)利器,并無任何問題。但在C++中,由于存 在了繼承,以及繼承類的隱式類型轉(zhuǎn)換,使得這種原本滋補(bǔ)的特性成為了一劑毒藥。換句話說,C++所引入的動(dòng)多態(tài)破壞了C的直觀性。

            舍棄之后

                從上面的分析來看,動(dòng)多態(tài)同C的編程模型是不相容的。因而如果希望得到C的直觀性,并且消除C++的缺陷,必須放棄動(dòng)多態(tài)這個(gè)特性。現(xiàn)在來看看放棄之后將會(huì)怎樣。
                一旦放棄了動(dòng)多態(tài),也就放棄了虛函數(shù)和vtable。此時(shí),所有的對(duì)象都是pod了。那么首當(dāng)其沖的好處,就是可以進(jìn)行非侵入的序列化、hash計(jì)算等等 操作。由于對(duì)象肯定是連續(xù)分布的,可以直接地將對(duì)象取出進(jìn)行編碼、存儲(chǔ)、計(jì)算和傳輸,而無需了解對(duì)象內(nèi)部的數(shù)據(jù)結(jié)構(gòu)和含義。另外一個(gè)重要的問題也會(huì)得到解 決,這就是ABI。在C中統(tǒng)一的ABI很自然地存在于語(yǔ)言中。我們可以很容易地用link將兩個(gè)不同編譯器編譯的模塊連接起來,而不會(huì)發(fā)生問題。但 是,C++中做不到,除非不再使用類而使用純C。目前C++還沒有統(tǒng)一的ABI,即便標(biāo)準(zhǔn)委員會(huì)有意建立這樣的規(guī)范,實(shí)現(xiàn)起來也絕非易事。但是,如果放棄 動(dòng)多態(tài)之后,對(duì)象的布局便回歸到C的形態(tài),從而使得ABI不再成為一個(gè)問題。
                另一方面,隨著動(dòng)多態(tài)的取消,那么繼承的作用被僅僅局限于代碼復(fù)用,不再具有構(gòu)造接口的作用。我們前面已經(jīng)看到,繼承類向基類的隱式轉(zhuǎn)換,是為了使基類能 夠順利地成為繼承類的接口。既然放棄了動(dòng)多態(tài),那么也就無需基類再承擔(dān)接口的任務(wù)。那么由繼承類向基類的隱式類型轉(zhuǎn)換也可以被禁止:
                void func(A* a);
                B b;
                func(&b);  //編譯錯(cuò)誤,類型不匹配
                進(jìn)而對(duì)象切割也不會(huì)發(fā)生:
                B ba[5];
                func(ba); //編譯錯(cuò)誤,類型不匹配
                盡管根據(jù)數(shù)組-指針的等價(jià)性,ba可以被隱式地轉(zhuǎn)換為B*,但是B*不再能夠隱式地轉(zhuǎn)換為A*,從而避免了對(duì)象的切割。
                問題是,如此簡(jiǎn)單地將動(dòng)多態(tài)放棄掉,就如同將水和孩子一起潑掉那樣,實(shí)際上放棄了動(dòng)多態(tài)帶來的好處。實(shí)際上并非如此。我們放棄動(dòng)多態(tài)這個(gè)特性,但并不打算放棄它所具有的功能,而是用另一種技術(shù)加以替代。這便是runtime concept(這里這里)。
                不同于以類型為基礎(chǔ)的interface,concept是獨(dú)立于類型的系統(tǒng)。concept生來便是為了描述一組類型,因而是接口最理想的實(shí)現(xiàn)手段。當(dāng)concept runtime化之后,便具有了與動(dòng)多態(tài)相同的功能(很多方面還有所超越)。
                runtime concept同樣需要類似vtable的函數(shù)分派表,但由于它不是類型,這些分派表無需存放在對(duì)象內(nèi)部,可以獨(dú)立放置(可以同RTTI信息放在一起), 并且只需一份。正是基于這個(gè)特性,方才保證了所有對(duì)象依然是pod,依然能夠保證對(duì)象布局的直觀性。
                同樣,runtime concept承擔(dān)了接口的任務(wù),但不象動(dòng)多態(tài)那樣依賴于繼承和相應(yīng)的隱式類型轉(zhuǎn)換。(通過自動(dòng)或手動(dòng)的concept_map)。因而,我們依舊可以禁止基于繼承關(guān)系的隱式類型轉(zhuǎn)換,從而防止對(duì)象切割的情況。
                一旦使用concept作為多態(tài)的實(shí)現(xiàn)手段,反倒促使原本動(dòng)多態(tài)的一些麻煩得到消除。在動(dòng)多態(tài)中,必須指定virtual函數(shù)。如此,在一個(gè)類中會(huì)存在兩 種不同形態(tài)的函數(shù),實(shí)現(xiàn)動(dòng)多態(tài)的虛函數(shù),和無此功能的普通函數(shù)。準(zhǔn)確地維護(hù)這樣兩種函數(shù),頗有些難度。而且,函數(shù)是虛還是不虛,牽涉到系統(tǒng)的設(shè)計(jì),必須在 最初構(gòu)建時(shí)確定,否則以后很難修改。但在放棄動(dòng)多態(tài),使用concept的情況下,只要一個(gè)繼承類中,使用相同的簽名覆蓋基類中的函數(shù),便實(shí)現(xiàn)了多態(tài)。當(dāng) 進(jìn)行concept_map,即將接口與類綁定時(shí),只會(huì)考慮繼承類的函數(shù),而忽略基類中被覆蓋的函數(shù)。于是,只需簡(jiǎn)單的覆蓋,便實(shí)現(xiàn)了多態(tài)的控制。對(duì)于是 否多態(tài)一個(gè)函數(shù),即是否改變基類函數(shù)的行為,完全由繼承類控制,在創(chuàng)建基類時(shí)不必為此傷神。其結(jié)果就是,我們無需在系統(tǒng)設(shè)計(jì)的最初一刻就操心多態(tài)的問題, 而只需根據(jù)實(shí)現(xiàn)的需要隨時(shí)實(shí)現(xiàn)。

            其他

                存在大量隱式轉(zhuǎn)換也是C++常受人詬病的一個(gè)方面,(特別是那些Pascal系的程序員)。隱式轉(zhuǎn)換的目的是帶來方便,使得編碼更加簡(jiǎn)潔,減少冗余。同時(shí)也使得一些技巧得以施行。但是,隱式轉(zhuǎn)換的副作用也頗為可觀。比如:
                void fun(short a);
                long a=1248;
                fun(a); //頂多一個(gè)警告
                這種轉(zhuǎn)換存在兩面性:一方面,它可能是合理的,因?yàn)楸M管a類型long大于short,但很可能存放著short可容納的數(shù)值;但另一方面,a的確存在short無法容納的可能性,這便會(huì)造成一個(gè)非常隱蔽的bug。
                C/C++對(duì)此的策略是把問題扔給程序員處理,如果有bug那是程序員的問題。這也算得上合情合理,畢竟有所得必有所失,也符合C/C++的一貫理念。但 終究不是最理想的方式。但是如果象Pascal那樣將類型管得很死,那么語(yǔ)言又會(huì)失去靈活性,使得開發(fā)的復(fù)雜性增加。
                如果試圖禁止隱式類型轉(zhuǎn)換,那么為了維持函數(shù)使用代碼的簡(jiǎn)潔性,函數(shù)必須對(duì)所有的類型執(zhí)行重載。這大大增加了函數(shù)實(shí)現(xiàn)的負(fù)擔(dān),并且重復(fù)的代碼嚴(yán)重違背了DRY原則。
                現(xiàn)在或許存在一些途徑,使得在維持絕對(duì)強(qiáng)類型的情況下獲得所希望的靈活性。鑰匙可能依然在concept手上。考慮如下的代碼:
                void fun(Integers a);
                long a=1248;
                fun(a);
                longlong b=7243218743012;
                fun(b);
                此處,fun()是一個(gè)函數(shù),它的形參是一個(gè)concept,代表了所有的整型。這樣,這個(gè)函數(shù)便可以接受任何一種整型(或者具有整型行為的類型)。我們 相信,在一般的應(yīng)用下,任何整數(shù)都有完全相同的行為。因此,我們便可以很方便地使用Integers這個(gè)接口執(zhí)行對(duì)整數(shù)的操作,而無需關(guān)心到底是什么樣的 整數(shù)。
                如此,我們便可以在禁止隱式類型轉(zhuǎn)換,不使用函數(shù)重載的情況下,完成這種函數(shù)的編寫。同時(shí)可以得到更好的類型安全性。

                強(qiáng)制類型轉(zhuǎn)換是非常重要的特性,特別是在底層開發(fā)時(shí)。但也是雙刃劍,往往引來很隱蔽的錯(cuò)誤。強(qiáng)制類型轉(zhuǎn)換很多情況下是無理的,通常都是軟件的設(shè)計(jì)問題造成的。但終究還是有一些情況,需要它來處理。
                設(shè)想這樣一個(gè)場(chǎng)景:兩個(gè)一模一樣的類型,但它們分屬不同的函數(shù)。(這種情形盡管不多見,但還是存在的。這往往是混亂設(shè)計(jì)的結(jié)果。當(dāng)然也有合理的情況,比如 來自兩個(gè)不同庫(kù)的類型)。我現(xiàn)在需要寫一個(gè)函數(shù),能夠同時(shí)使用這兩個(gè)類型。比較安全一些的,可以用函數(shù)重載。但是兩個(gè)重載的函數(shù)代碼是一樣的,典型的冗余 代碼。當(dāng)然也可以針對(duì)其中一個(gè)結(jié)構(gòu)編寫代碼,然后在使用時(shí),對(duì)另一個(gè)結(jié)構(gòu)的實(shí)例執(zhí)行強(qiáng)制類型轉(zhuǎn)換。但是,強(qiáng)制類型轉(zhuǎn)換畢竟不是件好事。因此,我們也可以構(gòu) 造一個(gè)concept,讓它描述這兩個(gè)類型。然后在編寫函數(shù)時(shí)使用這個(gè)concept,當(dāng)這兩個(gè)類型都與concept綁定后,便可以直接使用這兩個(gè)類 型,而沒有類型安全和代碼冗余的問題。
                (順便提一下,這種方式也可以運(yùn)用在類型不同的情況下。比如兩個(gè)類型不完全相同,但是基本要素都一樣。那么就可以使用concept_map的適配功能, 將兩個(gè)類型統(tǒng)一在一個(gè)concept下。這種方式相比oop的Adapter模式,更加簡(jiǎn)潔。adapter本身是一個(gè)container,它所實(shí)現(xiàn)的接 口函數(shù),都必須一一轉(zhuǎn)發(fā)到內(nèi)部的對(duì)象,編寫起來相當(dāng)繁瑣。但在concept_map中,對(duì)于那些符合concept描述的函數(shù)無需另行處 理,concept會(huì)自動(dòng)匹配,只需對(duì)那些不符合要求的函數(shù)執(zhí)行適配。)

                前面說過,指針數(shù)組的等價(jià)性體現(xiàn)了一種直觀的編程模型。但是,指針和數(shù)組畢竟還是存在很多差別,比如指針僅僅表達(dá)了一組對(duì)象在內(nèi)存中的位置,但并未攜帶對(duì)象數(shù)量的信息。因而,當(dāng)數(shù)組退化成指針時(shí),便已經(jīng)失去了數(shù)組的身份:
                void func(int* x);
                int a[20];
                func(a);
                這里,在函數(shù)func中已經(jīng)無法將a作為數(shù)組處理,因?yàn)闊o法知道變成int*后的a有多大來避免越界。甚至我們無法把a(bǔ)作為多個(gè)對(duì)象構(gòu)成的內(nèi)存塊看待,因?yàn)槲覀儾恢来笮 R虼耍挥酗@式地給出數(shù)組大小,才能使用:
                void func(int* x, long size);
                但是,在concept的作用下,數(shù)組和指針得以依然保持它們的等價(jià)性的情況下,解決數(shù)組退化問題。考慮這樣兩個(gè)函數(shù):
                void func1(Pointer x);
                void func2(Container x);
                其中,Pointer是代表指針的concept,而Container則是代表容器的concept。必須注意的是,Pointer是嚴(yán)格意義上的指 針,也就是說無法在Pointer上執(zhí)行迭代操作。Pointer只能作為指針使用,只具備dereference的能力(很像java的“指針”,不是 嗎?concept在沒有放棄C的底層特性的情況下也做到了。)。而Container則是專門用來表達(dá)容器的concept,其基本的特性便是迭代。在 func1中,無法對(duì)形參x執(zhí)行迭代,僅僅將其作為指向一個(gè)對(duì)象的指針處理,保證其安全性。而對(duì)于需要進(jìn)行迭代操作的func2而言,x則是可以遍歷的。 于是,對(duì)于同一個(gè)數(shù)組a,兩個(gè)函數(shù)分別從不同的角度對(duì)其進(jìn)行處理:
                int a[20];
                func1(a); //a直接作為指針處理,但不能迭代
                func2(a); //a作為容器處理,可以迭代,并且其尺寸信息也一同傳入
                此處實(shí)際上是利用了concept對(duì)類型特性的描述作用,將具有兩重性的數(shù)組類型(數(shù)組a即代表了數(shù)組這個(gè)容器,也代表了數(shù)組的起始地址)以不同特征加以 表達(dá),以滿足不同應(yīng)用的需求。數(shù)組仍然可以退化成指針,C的直觀模型得到保留,在很多特殊的場(chǎng)合發(fā)揮作用。但在其他應(yīng)用場(chǎng)景,可以更加安全地使用數(shù)組。
               

            總結(jié)

                綜上所述,C++未能真正延續(xù)C的直觀簡(jiǎn)潔,主要是由于動(dòng)多態(tài)的一些基礎(chǔ)設(shè)施破壞了C的編程模型。因而,我們可以通過放棄動(dòng)多態(tài),及其相關(guān)的一些技術(shù),代 之以更加“和諧”的runtime concept,使得C++在基本保留C的編程模型的同時(shí),獲得了相比原來更好的軟件工程特性。至此,這種改變后的C++(如果還能稱為C++的話)擁有 如下的主干特性:
                1、SP,來自于C。
                2、完全pod化。
                3、OB。保留了封裝和RAII。盡管也保留了繼承,但其作用僅限于代碼復(fù)用,禁止基于繼承的隱式類型轉(zhuǎn)換。
                4、GP,包括static和runtime concept。這是抽象高級(jí)特性的核心和基石。
                這樣的語(yǔ)言特性實(shí)質(zhì)上比現(xiàn)有的C++更加簡(jiǎn)潔,但是其能力更加強(qiáng)大。也比C++更易于貼近C的編程模型,以便適應(yīng)底層的開發(fā)。我不能說這樣的變化是否會(huì)產(chǎn)生一個(gè)更好的語(yǔ)言,但是我相信這些特性有助于構(gòu)造更加均衡統(tǒng)一的語(yǔ)言。
            posted @ 2008-08-02 20:57 longshanks 閱讀(3002) | 評(píng)論 (14)編輯 收藏

            2008年7月26日 #

            GP技術(shù)的展望——先有鴻鈞后有天

            莫華楓


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

            泛型和類型

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

            Concept和類型

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

            Concept、類型和對(duì)象

                事情還可以有更加夸張的發(fā)展。前面對(duì)泛型進(jìn)行了特化,能不能對(duì)類型也來一番“特化”呢?當(dāng)然可以:
                void fun(int a);
                void fun(int a:a==0); //對(duì)于類型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
                實(shí)際上,這無非是在參數(shù)聲明之后加上一組約束條件,用以表明該版本函數(shù)的選擇條件。沒有約束的函數(shù)版本在沒有任何約束條件匹配的情況下被選擇。對(duì)于使用立 即數(shù)或者靜態(tài)對(duì)象的調(diào)用而言,函數(shù)的選擇在編譯期執(zhí)行,編譯器根據(jù)條件直接調(diào)用匹配的版本。對(duì)于變量作為實(shí)參的調(diào)用而言,則需要展開,編譯器將自動(dòng)生成如 下代碼:
                //首先將函數(shù)重新命名,賦予唯一的名稱
                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
                //然后構(gòu)造分派函數(shù)
                void fun_d(int a) {
                    if(a==0)
                        fun_2(a);
                    else if(a>200)
                        fun_3(a);
                    ...
                    else
                        fun_1(a);
                }
                在某些情況下,可能需要對(duì)一個(gè)對(duì)象的成員做出約束,此時(shí)便可以采用這種形式:
                struct A
                {
                    float x;
                };
                ...
                void fun(A a:a.x>39.7);
                ...
                這種施加在類型上的所謂“特化”實(shí)際上只是一種語(yǔ)法糖,只是由編譯器自動(dòng)生成了分派函數(shù)而已。這個(gè)機(jī)制在Haskell等語(yǔ)言中早已存在,并且在使用上帶 來很大的靈活性。如果沒有這種機(jī)制,那么一旦需要增加函數(shù)分派條件,那么必須手工修改分派函數(shù)。如果這些函數(shù),包括分派函數(shù),是第三方提供的代碼,那么修 改將是很麻煩的事。而一旦擁有了這種機(jī)制,那么只需添加一個(gè)相應(yīng)的函數(shù)重載即可。
                當(dāng)concept-類型重載和類型-對(duì)象重載混合在一起時(shí),便體現(xiàn)更大的作用:
                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-類型-對(duì)象重載體系遵循一個(gè)原則:優(yōu)先選擇匹配的函數(shù)中最特化的。這實(shí)際上是類型重載規(guī)則的擴(kuò)展。大的來說,所有類型比所屬的 concept更加特化,所有對(duì)象約束比所屬的類型更加特化。對(duì)于concept而言,如果concept A refine自concept B,那么A比B更加特化。同樣,如果一個(gè)類型的約束強(qiáng)于另一個(gè),那么前一個(gè)就比后一個(gè)更加特化,比如a==20比a>10更加特化。綜合起來,可以 有這樣一個(gè)抽象的規(guī)則:兩個(gè)約束(concept,或者施加在對(duì)象上的約束)A和B,作用在類型或者對(duì)象上分別產(chǎn)生集合,如果A產(chǎn)生的集合是B產(chǎn)生的集合 的真子集,那么便認(rèn)為A比B更加特化。
                根據(jù)這些規(guī)則,實(shí)際上可以對(duì)一個(gè)函數(shù)的重載構(gòu)造出一個(gè)“特化樹”:

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

            類模板

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

            從本質(zhì)上講...

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

            總結(jié)

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

            2008年2月26日 #

                 摘要: C++的營(yíng)養(yǎng) 莫華楓     上一篇《C++的營(yíng)養(yǎng)——RAII》中介紹了RAII,以及如何在C#中實(shí)現(xiàn)。這次介紹另一個(gè)重要的基礎(chǔ)技術(shù)——swap手法。 swap手法     swap手法不應(yīng)當(dāng)是C++獨(dú)有的技術(shù),很多語(yǔ)言都可以實(shí)現(xiàn),并且從中得到好處。只是C++存在的一些缺陷迫使大牛們發(fā)掘,并開始重視這種有用的手法。這 個(gè)原...  閱讀全文
            posted @ 2008-02-26 15:16 longshanks 閱讀(3891) | 評(píng)論 (3)編輯 收藏

            2008年2月16日 #

                 摘要: C++的營(yíng)養(yǎng) 莫華楓     動(dòng)物都會(huì)攝取食物,吸收其中的營(yíng)養(yǎng),用于自身生長(zhǎng)和活動(dòng)。然而,并非食物中所有的物質(zhì)都能為動(dòng)物所吸收。那些無法消化的物質(zhì),通過消化道的另一頭(某些動(dòng) 物消化道只有一頭)排出體外。不過,一種動(dòng)物無法消化的排泄物,是另一種動(dòng)物(生物)的食物,后者可以從中攝取所需的營(yíng)養(yǎng)。    一門編程語(yǔ)言,對(duì)于程序...  閱讀全文
            posted @ 2008-02-16 08:19 longshanks 閱讀(2055) | 評(píng)論 (2)編輯 收藏

            2008年2月14日 #

            瓦格納的排場(chǎng)

                這個(gè)春節(jié)過的實(shí)在無趣。走完親戚,招待完親戚,逛街買好東西,就沒多少時(shí)間了。看書的興致也沒了。想寫點(diǎn)什么,總是沒法集中精力。實(shí)在膩味了,把以前下載的瓦格納的歌劇《尼伯龍根指環(huán)》拿出來看看。自從下載,沒怎么好好看過,這回算是補(bǔ)上了。
                瓦格納的“指環(huán)”系列可以算是歌劇里的極品,總共四出:萊茵黃金、女武神、齊格佛雷德和眾神的黃昏。分成四個(gè)晚上連演,總共加起來大約15個(gè)小時(shí)。不說別 的,里面的角色眾多,光神就有8個(gè),人有7個(gè),女武神9個(gè),尼伯龍根矮人2個(gè),還有三個(gè)仙女、2個(gè)巨人,和一只小鳥(在后臺(tái)的女高音)。情節(jié)錯(cuò)綜復(fù)雜,音 樂更是宏大。資料上說,這部歌劇中有200多個(gè)動(dòng)機(jī)組合、交織在一起。不過,這還不能表現(xiàn)出瓦格納在音響上近乎變態(tài)的追求。指環(huán)系列要求一個(gè)超過100人 龐大的樂團(tuán),并且引入了幾種新的樂器,包括 Wagner tuba, bass trumpet和contrabass trombone。其中, Wagner tuba還是他為這部歌劇專門發(fā)明的。最夸張的是,瓦格納為了獲得如此龐大的樂隊(duì)同演員聲音之間的平衡,專門建造了一座歌劇院,也就是著名的拜羅伊特節(jié)日劇院(Bayreuth Festspielhaus)。這在音樂史上是絕無僅有的。正是由于這種駭人的排場(chǎng),造就了歌劇史上的巔峰之作。
                這倒讓我聯(lián)想到C++。說實(shí)在的,C++的使用,有時(shí)也同瓦格納譜寫的歌劇那樣,非常復(fù)雜、龐大,需要大量的投入,和前期準(zhǔn)備。有時(shí),為了一些應(yīng)用,而去構(gòu)造一些的基礎(chǔ)設(shè)施(就像
            拜羅伊特節(jié)日劇院)。這種龐大導(dǎo)致了應(yīng)用面的狹窄,但是卻能夠獲得極品般的東西。這種東西當(dāng)然不會(huì)是到處都有,但卻是強(qiáng)大的、偉大的,以及關(guān)鍵性的。
                當(dāng)然,排場(chǎng)僅僅是表面的東西,真正吸引人的,還是瓦格納的音樂。歌劇在瓦格納手里,不再是一系列的詠嘆調(diào)。瓦格納是在用音樂講故事。音樂是歌劇的一部分, 歌唱是音樂的一部分,布景、燈光和舞臺(tái)效果都是不可分割的一分子。所有這些是一個(gè)整體,除了個(gè)別出彩的樂段(基本上只有“飛馳的女武神”這一段,曾被用在 電影“現(xiàn)代啟示錄”中),很少單獨(dú)演奏。它們戲劇性太強(qiáng)了,脫離了歌劇,就僅僅是一堆音符而已。
                這一點(diǎn)上,C++也是如此,語(yǔ)言、庫(kù)、慣用法等等,都是整體,一旦相互脫離,便無法發(fā)揮應(yīng)有的作用。所以孤立地運(yùn)用C++某一方面的特性,往往會(huì)誤入歧途,只有綜合運(yùn)用各種手段,才能真正地用好C++。
                瓦格納龐大復(fù)雜和莫扎特的簡(jiǎn)潔優(yōu)雅形成了鮮明的對(duì)比。但是我們不能說莫扎特比瓦格納更好,或者反過來。他們的音樂都是最偉大的杰作,一個(gè)人可以毫無沖突地 同時(shí)成為他們倆人的粉絲(就像我:))。我們總能從中獲得想像、思考、思想和倫理,兩者都具備無法替代的營(yíng)養(yǎng)。認(rèn)真地學(xué)習(xí)和吸收,才是正道。
                編程語(yǔ)言的學(xué)習(xí)和使用,又何嘗不是如此呢?

            注:嚴(yán)格地說,瓦格納的這些作品并不是歌劇,有一個(gè)正式的名稱:music drama,直接翻譯是“音樂戲劇”,是歌劇的擴(kuò)展。但是為了方便,還是廣義上地將其稱作歌劇。
            posted @ 2008-02-14 11:25 longshanks 閱讀(1204) | 評(píng)論 (1)編輯 收藏

            2008年1月25日 #

            當(dāng)GPL遇上MP

            莫華楓

                GPL,也就是General Purpose Language,是我們使用的最多的一類語(yǔ)言。傳統(tǒng)上,GPL的語(yǔ)法,或者特性,是固態(tài)的。然而,程序員都是聰明人(即便算不上“最聰明”,也算得上 “很聰明”吧:)),往往不愿受到語(yǔ)法的束縛,試圖按自己的心意“改造”語(yǔ)言。實(shí)際上,即便是早期的語(yǔ)言,也提供了一些工具,供聰明人們玩弄語(yǔ)法。我看的第一本C語(yǔ)言的書里,就有這么一個(gè)例子,展示出這種“邪惡”的手段:
                  #define procedure void
                  #define begin {
                  #define end }
                然后:
                  procedure fun(int x)
                  begin
                      ...
                  end
                實(shí)際上,它的意思是:
                  void fun(int x)
                  {
                      ...
                  }
                這可以看作是對(duì)初學(xué)C語(yǔ)言的Pascal程序員的安慰。這種蹩腳的戲法可以算作元編程的一種,在一種語(yǔ)言里創(chuàng)造了另一個(gè)語(yǔ)法。不過,實(shí)在有些無聊。然而,在實(shí)際開發(fā)中,我們或多或少地會(huì)需要一些超出語(yǔ)法范圍的機(jī)制。有時(shí)為了完善語(yǔ)言,彌補(bǔ)一些缺憾;有時(shí)為了增強(qiáng)一些功能;有時(shí)為了獲得一些方便。更新潮的,是試圖在一種GPL里構(gòu)建Domain Specific Language,或者說“子語(yǔ)言”,以獲得某個(gè)特性領(lǐng)域上更直觀、更簡(jiǎn)潔的編程方式。這些對(duì)語(yǔ)言的擴(kuò)展需求的實(shí)現(xiàn),依賴于被稱為Meta- Programming(MP)的技術(shù)。
                另一方面,隨著語(yǔ)言功能和特性的不斷增加,越來越多的人開始抱怨語(yǔ)言太復(fù)雜。一方面:“難道我們會(huì)需要那些一輩子也用不到幾回的語(yǔ)言機(jī)制,來增加語(yǔ)言的復(fù)雜性和學(xué)習(xí)使用者的負(fù)擔(dān)嗎?”。另一方面:“有備無患,一個(gè)語(yǔ)言機(jī)制要到迫在眉睫的時(shí)候才去考慮嗎?”。但MP技術(shù)則將這對(duì)矛盾消弭于無形。一種語(yǔ)言,可以簡(jiǎn)潔到只需最基本的一些特性。而其他特定的語(yǔ)言功能需求,可以通過MP加以擴(kuò)展。如果不需要某種特性,那么只要不加載相應(yīng)的MP代碼即可,而無需為那些機(jī)制而煩惱。
                MP最誘人的地方,莫過于我們可以通過編寫一個(gè)代碼庫(kù)便使得語(yǔ)言具備以往沒有的特性。
                然而,全面的MP能力往往帶來巨大的副作用,以至于我們無法知道到底是好處更多,還是副作用更多。語(yǔ)言的隨意擴(kuò)展往往帶來某些危險(xiǎn),比如語(yǔ)法的沖突和不兼容,對(duì)基礎(chǔ)語(yǔ)言的干擾,關(guān)鍵字的泛濫等等。換句話說,MP是孫悟空,本領(lǐng)高強(qiáng)。但沒有緊箍咒,是管不住他的。
                那么,緊箍咒是什么呢?這就是這里打算探討的主題。本文打算通過觀察兩種已存在的MP技術(shù),分析它們的特點(diǎn)與缺陷,從中找出解決問題的(可能)途徑。

            AST宏

                首先,先來看一下宏,這種遠(yuǎn)古時(shí)代遺留下來的技術(shù)。以及它的后裔,ast宏。
                關(guān)于傳統(tǒng)的宏的MP功能,上面的代碼已經(jīng)簡(jiǎn)單地展示了。但是,這種功能是極其有限的。宏是通過文本替換的形式,把語(yǔ)言中的一些符號(hào)、操作符、關(guān)鍵字等等替換成另一種形式。而對(duì)于復(fù)雜的語(yǔ)法構(gòu)造的創(chuàng)建無能為力。問題的另一面,宏帶來了很多副作用。由于宏的基礎(chǔ)是文本替換,所以幾乎不受語(yǔ)法和語(yǔ)義的約束。而且,宏的調(diào)試?yán)щy,通常也不受命名空間的約束。它帶來的麻煩,往往多于帶來的好處。
                ast宏作為傳統(tǒng)宏的后繼者,做了改進(jìn),使得宏可以在ast(Abstract Syntax Tree)結(jié)構(gòu)上執(zhí)行語(yǔ)法的匹配。(這里需要感謝TopLanguage上的Olerev兄,他用簡(jiǎn)潔而又清晰的文字,對(duì)我進(jìn)行了ast宏的初級(jí)培訓(xùn):))。這樣,便可以創(chuàng)造新的語(yǔ)法:
                  syntax(x, "<->", y, ";")
                  {
                      std::swap(x, y);
                  }
                當(dāng)遇到代碼:
                  x <-> y;
                的時(shí)候,編譯器用std::swap(x,y);加以替換。實(shí)際上,這是將一種語(yǔ)法結(jié)構(gòu)映射到另一個(gè)語(yǔ)法結(jié)構(gòu)上。而ast宏則是這種映射的執(zhí)行者。
                但是,ast宏并未消除宏本身的那些缺陷。如果x或者y本身不符合swap的要求(類型相同,并且能復(fù)制構(gòu)造和賦值,或者擁有swap成員函數(shù)),那么 ast宏調(diào)用的時(shí)候無法對(duì)此作出檢驗(yàn)。宏通常以預(yù)編譯器處理,ast宏則將其推遲到語(yǔ)法分析之時(shí)。但是此時(shí)依然無法得到x或y的語(yǔ)義特征,無法直接在調(diào)用點(diǎn)給出錯(cuò)誤信息。
                同時(shí),ast宏還是無法處理二義性的語(yǔ)法構(gòu)造。如果一個(gè)ast宏所定義的語(yǔ)法構(gòu)造與主語(yǔ)言,或者其他ast宏的相同,則會(huì)引發(fā)混亂。但是,如果簡(jiǎn)單粗暴地將這種“重定義”作為非法處理,那么會(huì)大大縮小ast宏(以及MP)的應(yīng)用范圍。實(shí)際上,這種語(yǔ)法構(gòu)造的重定義有其現(xiàn)實(shí)意義,可以看作一種語(yǔ)法構(gòu)造的“重載”,或者函數(shù)(操作符)重載的一種擴(kuò)展。
                解決的方法并不復(fù)雜,就是為ast宏加上約束。實(shí)際上,類似的情形在C++98的模板上也存在,而C++則試圖通過為模板添加concept約束加以解決。這種約束有兩個(gè)作用:其一,在第一時(shí)間對(duì)ast宏的使用進(jìn)行正確性檢驗(yàn),而無需等到代碼展開之后;其二,用以區(qū)分同一個(gè)語(yǔ)法構(gòu)造的不同版本。
                于是,對(duì)于上述例子可以這樣施加約束(這些代碼只能表達(dá)一個(gè)意思,還無法看作真正意義上的MP語(yǔ)法):
                  syntax(x, "<->", y, ";")
                       where x,y is object of concept (has_swap_mem or (CopyConstructable and Assignable))
                            && typeof(x)==typeof(y)
                  {
                      std::swap(x,y);
                  }
                如此,除非x,y都是對(duì)象,并且符合所指定的concept,否則編譯器會(huì)當(dāng)即加以拒絕,而且直截了當(dāng)。
                不過,如此變化之后,ast宏將不會(huì)再是宏了。因?yàn)檫@種約束是語(yǔ)義上的,必須等到語(yǔ)義分析階段,方能檢驗(yàn)。這就超出了宏的領(lǐng)地了。不過既然ast宏可以從預(yù)處理階段推遲到語(yǔ)法分析階段,那么再推遲一下也沒關(guān)系。再說,我們關(guān)注的是這種功能,帶約束的ast宏到底是不是宏,也無關(guān)緊要。

            TMP

                下面,我們回過頭,再來看看另一種MP技術(shù)——TMP(參考David Abrahams, Aleksey Gurtovoy所著的《C++ Template Metaprogramming》)。對(duì)于TMP存在頗多爭(zhēng)議,支持者認(rèn)為它提供了更多的功能和靈活性;反對(duì)者認(rèn)為TMP過于tricky,難于運(yùn)用和調(diào)試。不管怎么樣,TMP的出現(xiàn)向我們展示了一種可能性,即在GPL中安全地進(jìn)行MP編程的可能性。由于TMP所運(yùn)用的都是C++本身的語(yǔ)言機(jī)制,而這些機(jī)制都是相容的。所以,TMP所構(gòu)建的 MP體系不會(huì)同GPL和其他子語(yǔ)言的語(yǔ)法機(jī)制相沖突。
                實(shí)際上,TMP依賴于C++的模板及其特化機(jī)制所構(gòu)建的編譯期計(jì)算體系,以及操作符的重載和模板化。下面的代碼摘自boost::spirit的文檔:
                  group = '(' >> expr >> ')';

                  expr1 = integer | group;

                  expr2 = expr1 >> *(('*' >> expr1) | ('/' >> expr1));

               expr = expr2 >> *(('+' >> expr2) | ('-' >> expr2));

                這里表達(dá)了一組EBNF(語(yǔ)法著實(shí)古怪,這咱待會(huì)兒再說):>>代表了標(biāo)準(zhǔn)EBNF的“followed by”,*代表了標(biāo)準(zhǔn)EBNF的*(從右邊移到左邊),括號(hào)還是括號(hào),|依舊表示Union。通過對(duì)這些操作符的重載,賦予了它們新的語(yǔ)義(即EBNF的相關(guān)語(yǔ)義)。然后配合模板的類型推導(dǎo)、特化等等機(jī)制,變戲法般地構(gòu)造出一個(gè)語(yǔ)法解析器,而且是編譯時(shí)完成的。

                盡管在spirit中,>>、*、|等操作符被挪作他用,但是我們依然可以在這些代碼的前后左右插入諸如:cin>> *ptrX;的代碼,而不會(huì)引發(fā)任何問題。這是因?yàn)?gt;>等操作符是按照不同的類型重載的,對(duì)于不同類型的對(duì)象的調(diào)用,會(huì)調(diào)用不同版本的操作符重載,互不干擾,老少無欺。

                但是,TMP存在兩個(gè)問題。其一,錯(cuò)誤處理不足。如果我不小心把第二行代碼錯(cuò)寫成:expr1 = i | group;,而i是一個(gè)int類型的變量,那么編譯器往往會(huì)給出一些稀奇古怪的錯(cuò)誤。無非就是說類型不匹配之類的,但是很晦澀。這方面也是TMP受人詬病的一個(gè)主要原因。好在C++0x中的concept可以對(duì)模板作出約束,并且在調(diào)用點(diǎn)直接給出錯(cuò)誤提示。隨著這些技術(shù)的引入,這方面問題將會(huì)得到緩解。

                其二,受到C++語(yǔ)法體系的約束,MP無法自由地按我們習(xí)慣的形式定義語(yǔ)法構(gòu)造。前面說過了,spirit的EBNF語(yǔ)法與標(biāo)準(zhǔn)EBNF有不小的差異,這對(duì)于spirit的使用造成了不便。同樣,如果試圖運(yùn)用TMP在C++中構(gòu)造更高級(jí)的DSL應(yīng)用,諸如一種用于記賬的帳務(wù)處理語(yǔ)言,將會(huì)遇到更大的限制。實(shí)際上TMP下的DSL也很少有令人滿意的。

                所以說,TMP在使用上的安全性來源于操作符復(fù)用(或重載)的特性。但是,操作符本身的語(yǔ)法特性是固定的,這使得依賴于操作符(泛化或非泛化)重載的TMP不可能成為真正意義上的MP手段。

                那么,對(duì)于TMP而言,我們感興趣的是它的安全性和相容性。而對(duì)其不滿的,則是語(yǔ)法構(gòu)造的靈活性。本著“去其糟粕,取其精華”的宗旨,我們可以對(duì)TMP做一番改進(jìn),以獲得更完整的MP技術(shù)。TMP的核心自然是模板(類模板和函數(shù)/操作符模板),在concept的幫助下,模板可以獲得最好的安全性和相容性。以此為基礎(chǔ),如果我們將模板擴(kuò)展到語(yǔ)法構(gòu)造上,那么便可以在保留TMP的安全性和相容性的情況下,獲得更大的語(yǔ)法靈活性。也就是說,我們需要增加一種模板——語(yǔ)法模板

                  template<typename T>
                  syntax synSwap=x "<->" y ";"
                     require SameType<decltype(x), T> && SameType<decltype(y), T>
                            && (has_swap_mem<T> || (CopyConstructable<T> and Assignable<T>)
                  {
                      std::swap(x, y);
                  }

                這里我杜撰了關(guān)鍵字syntax,并且允許為語(yǔ)法構(gòu)造命名,便于使用。而語(yǔ)法構(gòu)造描述,則是等號(hào)后面的部分。require沿用自C++0x的concept提案。稍作了些改造,允許concept之間的||。

            用戶定義的語(yǔ)法

                如果比較帶約束的ast宏和語(yǔ)法模板,會(huì)發(fā)現(xiàn)極其相似。實(shí)際上,兩者殊途同歸,展示了幾乎完全相同的東西。ast宏和TMP分別位于同一個(gè)問題的兩端,當(dāng)它們的缺陷得到彌補(bǔ)時(shí),便會(huì)相互靠攏,最終歸結(jié)到一種形式上。 

                現(xiàn)在,通過結(jié)合兩種方案的特色,我們便可以得到一個(gè)最終的MP構(gòu)造,既安全,又靈活:

                  syntax synSwap=x "<->" y ";";

                  where

                      (x,y is object)

                      && SameType<x, y>

                      && (has_swap_mem<x> || (CopyConstructable<x> and Assignable<x>))

                  {

                      std::swap(x, y);

                  }

                我去掉了template關(guān)鍵字,在約束(where)的作用下,template和類型參數(shù)列表都已經(jīng)沒有必要了。同時(shí),也允許直接將對(duì)象放入 concept:Assignable<x>,這相當(dāng)于:Assignable<decltype<x>>。

                “is ...”是特別的約束,用來描述那些超越concept范疇的特性。“...”可以是關(guān)鍵字“object”,表明相應(yīng)的標(biāo)識(shí)符是對(duì)象;也可以是關(guān)鍵字 “type”,表明相應(yīng)的標(biāo)識(shí)符是類型;或者是關(guān)鍵字“concept”,指定相應(yīng)的標(biāo)識(shí)符是concept等等。操作符的重載所涉及的參數(shù)只會(huì)是對(duì)象,只需對(duì)其類型做出約束,因此concept便足夠使用。但語(yǔ)法構(gòu)造的定義則廣大的多,它不僅僅會(huì)涉及對(duì)象,更可能涉及其它類型的語(yǔ)法要素。實(shí)際上,語(yǔ)法構(gòu)造所面對(duì)的參數(shù)是“標(biāo)識(shí)符”。那么一個(gè)標(biāo)識(shí)符上可能具備的各種特性,都應(yīng)當(dāng)作為約束的內(nèi)容。這里大致歸納出以下幾點(diǎn)需要約束的特性:

            1. 分類。對(duì)象、類型、concept、函數(shù)等等。is ...;
            2. 來源。來自哪個(gè)namespace。from ...;
            3. 尺寸。類型大小。sizeof(x)>20;
            4. 從屬。指定一個(gè)語(yǔ)法構(gòu)造是否從屬于其他語(yǔ)法構(gòu)造(其他語(yǔ)法構(gòu)造的一部分)。如果是從屬語(yǔ)法構(gòu)造,那么將不能單獨(dú)出現(xiàn)在獨(dú)立的語(yǔ)句中。更進(jìn)一步,可以強(qiáng)行指定一個(gè)語(yǔ)法構(gòu)造從屬于某個(gè)語(yǔ)法構(gòu)造,不允許在其他語(yǔ)法構(gòu)造中使用。belong_to ...;
            5. 類型及對(duì)象間的關(guān)系。諸如繼承、子對(duì)象、子類型、true typedef、別名、成員等等。
            6. ...

                也可以認(rèn)為,語(yǔ)法構(gòu)造的約束是concept的自然延伸。concept對(duì)類型做出約束,而語(yǔ)法構(gòu)造的約束的對(duì)象,則是標(biāo)識(shí)符。

                為了強(qiáng)化語(yǔ)法構(gòu)造的創(chuàng)建能力,應(yīng)當(dāng)允許在語(yǔ)法構(gòu)造的定義中使用BNF,或其他類似的語(yǔ)法描述語(yǔ)言。

                在語(yǔ)法構(gòu)造約束的支援下,便可以化解一些子語(yǔ)言同宿主語(yǔ)言之間的語(yǔ)法沖突。比如,我們創(chuàng)建了一種類似spirit的EBNF子語(yǔ)言,可以按照標(biāo)準(zhǔn)的EBNF形式編寫語(yǔ)法,構(gòu)造一個(gè)語(yǔ)法解析器:

                  syntax repeat1=p '+'

                  where

                      p is object of type(Parser)

                  {...}

                這樣便定義了一個(gè)后綴形式的+操作符,但僅僅作用于類型Parser的對(duì)象上。對(duì)于如下的代碼:

                  letter + number

                使用主語(yǔ)言的+(加),還是使用EBNF的+(重復(fù),至少一次),取決于letter和number的特征(這里是類型):

                  int letter, number;

                  letter + number; //使用主語(yǔ)言的+,表示letter加number

                  Parser letter, number;

                  letter + number; //使用EBNF的+,表示一串letter后面跟一個(gè)number

                如此,便使得用戶定義的語(yǔ)法不會(huì)同主語(yǔ)言的相同語(yǔ)法發(fā)生沖突。

                但是,語(yǔ)法構(gòu)造的約束并不能完全消除所有的語(yǔ)法沖突。其中一種情況便是常量的使用。比如:

                  'x' + 'y'

                它在宿主語(yǔ)言中,會(huì)被識(shí)別為兩個(gè)字符的相加。但在BNF子語(yǔ)言中,則會(huì)是若干個(gè)字符'x'之后緊跟一個(gè)‘y'。由于沒有類型、對(duì)象等代碼實(shí)體的參與,編譯器無法分辨使用哪種語(yǔ)言的語(yǔ)法規(guī)則來處理。解決的方法是使得常量類型化。這種做法在C++等語(yǔ)言中已經(jīng)運(yùn)用多年。通常采用為常量加前后綴:

                  'x'b_ + 'y'b_

                以此代表BNF子語(yǔ)言的常量。常量的語(yǔ)法定義可以是:

                  syntax bnf_const="'" letter "'" "b_";

                  where

                      is const of ... //...可以是某種類型

                  {...}

                通過is const of約束,將所定義的常量與某個(gè)類型綁定。而通過類型,編譯器便可以推斷出應(yīng)當(dāng)使用那種語(yǔ)法對(duì)其處理。

                然而,盡管帶約束的語(yǔ)法構(gòu)造定義可以在很大程度上消除語(yǔ)法沖突和歧義,但不可能消除所有的語(yǔ)法矛盾。另一方面,結(jié)構(gòu)相似,但語(yǔ)義完全不同的語(yǔ)法構(gòu)造混合在一起,即便不引發(fā)矛盾,也會(huì)對(duì)使用者造成難以預(yù)計(jì)的混亂。因此,在實(shí)際環(huán)境下,需要通過某種“語(yǔ)法圍欄”嚴(yán)格限定某種用戶定義語(yǔ)法的作用范圍。最直接的形式,就是通過namespace實(shí)現(xiàn)。namespace在隔離和協(xié)調(diào)命名沖突中起到很好的作用,可以進(jìn)一步將其運(yùn)用到MP中。由于namespace一經(jīng)打開,其作用范圍不會(huì)超出最內(nèi)層的代碼塊作用域:

                  {

                      using namespace XXX;

                      ...

                  } //XXX的作用范圍不會(huì)超出這個(gè)范圍

                運(yùn)用這個(gè)特性,可以很自然地將蘊(yùn)藏在一個(gè)namespace中的用戶定義語(yǔ)法構(gòu)造限制在一個(gè)確定的范圍內(nèi)。

                但是,僅此不夠。畢竟語(yǔ)法不同于命名,不僅會(huì)存在沖突,還會(huì)存在(合法的)混淆,而且往往是非常危險(xiǎn)的。為此,需要允許在using namespace的時(shí)候進(jìn)一步指定語(yǔ)法的排他性。比如:

                  {

                      using namespace XXX exclusive;

                      ...

                  }

                如果namespace中的用戶定義語(yǔ)法與外部語(yǔ)法(宿主語(yǔ)言,或外圍引用的namespace)重疊(沖突或混淆),外部語(yǔ)法將被屏蔽。更進(jìn)一步,可以允許不同級(jí)別的排他:排斥重疊和完全屏蔽。前者只屏蔽重疊的語(yǔ)法,這種級(jí)別通常用于擴(kuò)展性的用戶語(yǔ)法構(gòu)造(擴(kuò)展主語(yǔ)言,需要同主語(yǔ)言協(xié)同工作。而且語(yǔ)法重疊較少);而后者則將外部語(yǔ)法統(tǒng)統(tǒng)屏蔽,只保留所打開的namespace中的語(yǔ)法(子語(yǔ)言在代碼塊中是唯一語(yǔ)言),這種級(jí)別用于相對(duì)獨(dú)立的功能性子語(yǔ)言(可以獨(dú)立工作的子語(yǔ)言,通常會(huì)與主語(yǔ)言和其他子語(yǔ)言間存在較大的語(yǔ)法重疊,比如上述BNF子語(yǔ)言等等)。

                為提供更多的靈活性,可以在完全屏蔽的情況下,通過特定語(yǔ)句引入某種不會(huì)引發(fā)沖突的外部語(yǔ)法。比如:

                  {

                      using namespace XXX exclusive full;

                      using host_lang::syntax(...); //引入主語(yǔ)言的某個(gè)語(yǔ)法,...代表語(yǔ)法名稱

                      ...

                  }

                通常情況下,子語(yǔ)言不會(huì)完全獨(dú)立運(yùn)作,需要同宿主語(yǔ)言或其他子語(yǔ)言交互,也就是數(shù)據(jù)交換。主語(yǔ)言代碼可能需要將數(shù)據(jù)對(duì)象傳遞給子語(yǔ)言,處理完成后再取回。由于編譯器依賴于標(biāo)識(shí)符的特性(主要是類型)來選擇語(yǔ)法構(gòu)造。所以,一種子語(yǔ)言必須使用其內(nèi)部定義的類型和對(duì)象。為了能夠交換數(shù)據(jù),必須能夠把主語(yǔ)言的對(duì)象包裝成子語(yǔ)言的內(nèi)部類型。通過在子語(yǔ)言的namespace中進(jìn)行true typedef,可以將主語(yǔ)言類型定義成一個(gè)新的(真)類型,在進(jìn)入子語(yǔ)言作用域的時(shí)候,做顯式的類型轉(zhuǎn)換。另外,為了方便數(shù)據(jù)轉(zhuǎn)換,還可以采用兩種方法:將主語(yǔ)言類型map到一個(gè)子語(yǔ)言類型(相當(dāng)于給予“雙重國(guó)籍”),進(jìn)入子語(yǔ)言的語(yǔ)法范圍時(shí),相應(yīng)的對(duì)象在類型上具有兩重性,可以被自動(dòng)識(shí)別成所需的類型,但必須在確保不發(fā)生語(yǔ)法混淆的情況下使用;另一種穩(wěn)妥些的方法,可以允許執(zhí)行一種typedef,介于alias和true typedef之間,它定義了一個(gè)新類型,但可以隱式地同原來的類型相互轉(zhuǎn)換。數(shù)據(jù)交換的問題較復(fù)雜,很多問題尚未理清,有待進(jìn)一步考察。

            總結(jié)

                作為MP手段,ast宏擁有靈活性,而TMP則具備安全性,將兩者各自的優(yōu)點(diǎn)相結(jié)合,使我們可以獲得更加靈活和安全的語(yǔ)法構(gòu)造定義手段。通過在用戶定義的語(yǔ)法構(gòu)造上施加全面的約束,可以很好地規(guī)避語(yǔ)法沖突和歧義。

                但是,需要說明的是,這里所考察的僅僅局限在用戶定義語(yǔ)法構(gòu)造的沖突和歧義的消除上。GPL/MP要真正達(dá)到實(shí)用階段,還需要面對(duì)更多問題。比如,由于存在用戶定義的語(yǔ)法構(gòu)造,語(yǔ)法分析階段所面對(duì)的語(yǔ)法不是固態(tài)的,需要隨時(shí)隨地接受新語(yǔ)法,甚至重疊的語(yǔ)法(存在多個(gè)候選語(yǔ)法,不同的候選語(yǔ)法又會(huì)產(chǎn)生不同的下級(jí)語(yǔ)法),這就使語(yǔ)法分析大大復(fù)雜;語(yǔ)法模式匹配被推遲到語(yǔ)義分析階段,此前將無法對(duì)某些語(yǔ)法錯(cuò)誤作出檢驗(yàn);一個(gè)語(yǔ)法構(gòu)造的語(yǔ)義需要通過宿主語(yǔ)言定義,如何銜接定義代碼和周邊的環(huán)境和狀態(tài);如何為用戶定義的語(yǔ)法構(gòu)造設(shè)置出錯(cuò)信息;由于某些語(yǔ)法構(gòu)造的二義性,如何判別語(yǔ)法錯(cuò)誤屬于哪個(gè)語(yǔ)法構(gòu)造;...。此外還有一些更本質(zhì)性的問題,諸如語(yǔ)法構(gòu)造的重載和二義性是否會(huì)更容易誘使使用者產(chǎn)生更多的錯(cuò)誤等等,牽涉到錯(cuò)綜復(fù)雜的問題,需要更多的分析和試驗(yàn)。

                另外,作為MP的重要組成部分,編譯期計(jì)算能力也至關(guān)重要。TMP運(yùn)用了C++模板特化,D語(yǔ)言通過更易于理解的static_if等機(jī)制,都試圖獲得編譯期的計(jì)算能力,這些機(jī)制在完整的MP中需要進(jìn)一步擴(kuò)展,而并非僅僅局限在與類型相關(guān)的計(jì)算上。其他一些與此相關(guān)的特性,包括反射(編譯期和運(yùn)行期)、類型traits等,也應(yīng)作為MP必不可少的特性。

            posted @ 2008-01-25 15:09 longshanks 閱讀(1335) | 評(píng)論 (4)編輯 收藏

            2008年1月6日 #


            GP技術(shù)的展望——道生一,一生二

            by  莫華楓



                長(zhǎng)期以來,我們始終把GP(泛型編程)作為一種輔助技術(shù),用于簡(jiǎn)化代碼結(jié)構(gòu)、提高開發(fā)效率。從某種程度上來講,這種觀念是對(duì)的。因?yàn)槠駷橹梗珿P技術(shù)還只是一種編譯期技術(shù)。只能在編譯期發(fā)揮作用,一旦軟件完成編譯,成為可執(zhí)行代碼,便失去了利用GP的機(jī)會(huì)。對(duì)于現(xiàn)在的多數(shù)應(yīng)用而言,運(yùn)行時(shí)的多態(tài)能力顯得尤為重要。而現(xiàn)有的GP無法在這個(gè)層面發(fā)揮作用,以至于我這個(gè)“GP迷”也不得不灰溜溜地聲稱“用OOP構(gòu)建系統(tǒng),用GP優(yōu)化代碼”。

                然而,不久前,在TopLanguage group上的一次討論,促使我們注意到runtime GP這個(gè)概念。從中,我們看到了希望——使GP runtime化的希望——使得GP有望在運(yùn)行時(shí)發(fā)揮其巨大的威力,進(jìn)一步為軟件的設(shè)計(jì)與開發(fā)帶來更高的效率和更靈活的結(jié)構(gòu)。
                在這個(gè)新的系列文章中,我試圖運(yùn)用runtime GP實(shí)現(xiàn)一些簡(jiǎn)單,但典型的案例,來檢測(cè)runtime GP的能力和限制,同時(shí)也可以進(jìn)一步探討和展示這種技術(shù)的特性。

            運(yùn)行時(shí)多態(tài)

                現(xiàn)在的應(yīng)用側(cè)重于交互式的運(yùn)作形式,要求軟件在用戶輸入下作出響應(yīng)。為了在這種情況下,軟件的整體結(jié)構(gòu)的優(yōu)化,大量使用組件技術(shù),使得軟件成為“可組裝” 的系統(tǒng)。而接口-實(shí)現(xiàn)分離的結(jié)構(gòu)形式很好地實(shí)現(xiàn)了這個(gè)目標(biāo)。多態(tài)在此中起到了關(guān)鍵性的作用。其中,以O(shè)OP為代表的“動(dòng)多態(tài)”(也稱為 “subtyping多態(tài)”),構(gòu)建起在運(yùn)行時(shí)可調(diào)控的可組裝系統(tǒng)。GP作為“靜多態(tài)”,運(yùn)用泛化的類型體系,大大簡(jiǎn)化這種系統(tǒng)的構(gòu)建,消除重復(fù)勞動(dòng)。另外還有一種鮮為人知的多態(tài)形式,被《C++ Template》的作者David Vandevoorde和Nicolai M. Josuttis稱為runtime unbound多態(tài)。而原來的“動(dòng)多態(tài)”,即OOP多態(tài),被細(xì)化為runtime bound多態(tài);“靜多態(tài)”,也就是模板,則被稱為static unbound多態(tài)。
                不過這種稱謂容易引起誤解,主要就是unbound這個(gè)詞上。在這里unbound和bound是指在編寫代碼時(shí),一個(gè)symbol是否同一個(gè)具體的類型 bound。從這點(diǎn)來看,由于GP代碼在編寫之時(shí),面向的是泛型,不是具體的類型,那么GP是unbound的。因?yàn)楝F(xiàn)有的GP是編譯期的技術(shù),所以是 static的。OOP的動(dòng)多態(tài)則必須針對(duì)一個(gè)具體的類型編寫代碼,所以是bound的。但因?yàn)閯?dòng)多態(tài)可以在運(yùn)行時(shí)確定真正的類型,所以是runtime 的。至于runtime unbound,以往只出現(xiàn)在動(dòng)態(tài)語(yǔ)言中,比如SmallTalk、Python、Ruby,一種形象地稱謂是“duck-typing”多態(tài)。關(guān)于多態(tài)的更完整的分類和介紹可以看這里
                通過動(dòng)態(tài)語(yǔ)言機(jī)制實(shí)現(xiàn)的runtime unbound,存在的性能和類型安全問題。但當(dāng)我們將GP中的concept技術(shù)推廣到runtime時(shí)會(huì)發(fā)現(xiàn),rungime unbound可以擁有同OOP動(dòng)多態(tài)相當(dāng)?shù)男屎皖愋桶踩裕珔s具有更大的靈活性和更豐富的特性。關(guān)于這方面,我已經(jīng)寫過一篇文章 ,大致描述了一種實(shí)現(xiàn)runtime concept的途徑(本文的附錄里,我也給出了這種runtime concept實(shí)現(xiàn)的改進(jìn))。

            Runtime Concept

                Runtime concept的實(shí)現(xiàn)并不會(huì)很復(fù)雜,基本上可以沿用OOP中的“虛表”技術(shù),并且可以更加簡(jiǎn)單。真正復(fù)雜的部分是如何在語(yǔ)言層面表達(dá)出這種runtime GP,而不對(duì)已有的static GP和其他語(yǔ)言特性造成干擾。在這里,我首先建立一個(gè)基本的方案,然后通過一些案例對(duì)其進(jìn)行檢驗(yàn),在發(fā)現(xiàn)問題后再做調(diào)整。
                考慮到runtime concept本身也是concept,那么沿用static concept的定義形式是不會(huì)有問題的:
                  concept myconcept<T> {
                      T& copy(T& lhs, T const& rhs);
                      void T::fun();
                      ...
                  }
                具體的concept定義和使用規(guī)則,可以參考C++0x的concept提案這篇文章 ,以及這篇文章
                另一方面,我們可以通過concept_map將符合一個(gè)concept的類型綁定到該concept之上:
                  concept_map myconcept<MyType> {}
                相關(guān)內(nèi)容也可參考上述文件。
                有了concept之后,我們便可以用它們約束一個(gè)模板:
                  template<myconcept T>void myfun(T const& val); //函數(shù)模板
                  template<myconcept T>class X  //類模板
                  {
                       ...
                  };
                到此為止,runtime concept同static concept還是同一個(gè)事物。它們真正的分離在于使用。對(duì)于static concept應(yīng)用,我們使用一個(gè)具體的類型在實(shí)例化(特化)一個(gè)模板:
                  X<MyType> x1;  //實(shí)例化一個(gè)類模板
                  MyType obj1;
                  myfun(obj1);  //編譯器推導(dǎo)obj1對(duì)象的類型實(shí)例化函數(shù)模板
                  myfun<MyType>(obj1);  //函數(shù)模板的顯式實(shí)例化
                現(xiàn)在,我們將允許一種非常規(guī)的做法,以使runtime concept成為可能:允許使用concept實(shí)例化一個(gè)模板,或定義一個(gè)對(duì)象
                  X<myconcept> x2;
                  myconcept* obj2=new myconcept<MyType>;
                  myfun(obj2);  //此處,編譯器將會(huì)生成runtime版本的myfun
                這里的含義非常明確:對(duì)于x2,接受任何符合myconcept的類型的對(duì)象。obj2是一個(gè)“動(dòng)態(tài)對(duì)象”(這里將runtime concept引入的那種不知道真實(shí)類型,但符合某個(gè)concept的對(duì)象稱為“動(dòng)態(tài)對(duì)象”。而類型明確已知的對(duì)象成為“靜態(tài)對(duì)象”),符合myconcept要求。至于實(shí)際的類型,隨便,只要符合myconcept就行。
                這種情形非常類似于傳統(tǒng)動(dòng)多態(tài)的interface。但是,它們有著根本的差異。interface是一個(gè)具體的類型,并且要求類型通過繼承這種形式實(shí)現(xiàn)這個(gè)接口。而concept則不是一種類型,而是一種“泛型”——具備某種特征的類型的抽象(或集合),不需要在類型創(chuàng)建時(shí)立刻與接口綁定。與 concept的綁定(concept_map)可以發(fā)生在任何時(shí)候。于是,runtime concept實(shí)際上成為了一種非侵入的接口。相比interface這種侵入型的接口,更加靈活便捷。
                通過這樣一種做法,我們便可以獲得一種能夠在運(yùn)行時(shí)工作的GP系統(tǒng)。
                在此基礎(chǔ)上,為了便于后續(xù)案例展開,進(jìn)一步引入一些有用的特性:
            1. 一個(gè)concept的assosiate type被視為一個(gè)concept。一個(gè)concept的指針/引用(concept_id*/concept_id&,含義是指向一個(gè)符合concept_id的動(dòng)態(tài)對(duì)象,其實(shí)際類型未知),都被視作concept。一個(gè)類模板用concept實(shí)例化后,邏輯上也是一個(gè)concept。
            2. 動(dòng)態(tài)對(duì)象的創(chuàng)建。如果需要在棧上創(chuàng)建動(dòng)態(tài)對(duì)象,那么可以使用語(yǔ)法:concept_id<type_id> obj_id; 這里concept_id是concept名,type_id是具體的類型名,obj_id是對(duì)象名稱。這樣,便在棧上創(chuàng)建了一個(gè)符合concept_id的動(dòng)態(tài)對(duì)象,其實(shí)際類型是type_id
              如果需要在堆上創(chuàng)建動(dòng)態(tài)對(duì)象,那么可以用語(yǔ)法:concept_id* obj_id=new concept_id<type_id>; 這實(shí)際上可以看作“concept指針/引用”。
            3. concept推導(dǎo)(編譯期)。對(duì)于表達(dá)式concept_id obj_id=Exp,其中Exp是一個(gè)表達(dá)式,如果表達(dá)式Exp的類型是具體的類型,那么obj_id代表了一個(gè)靜態(tài)對(duì)象,其類型為Exp的類型。如果表達(dá)式Exp的類型是concept,那么obj_id是一個(gè)動(dòng)態(tài)對(duì)象,其類型為Exp所代表的concept。
              那么如何確定Exp是具體類型還是concept?可以使用這么一個(gè)規(guī)則:如果Exp中涉及的對(duì)象,比如函數(shù)的實(shí)參、表達(dá)式的操作數(shù)等等,只要有一個(gè)是動(dòng)態(tài)對(duì)象(類型是concept),那么Exp的類型就是concept;反之,如果所有涉及的對(duì)象都是靜態(tài)對(duì)象(類型為具體的類型),那么Exp的類型為相應(yīng)的具體類型。同樣的規(guī)則適用于concept*或concept&。
            4. concept轉(zhuǎn)換。類似在類的繼承結(jié)構(gòu)上執(zhí)行轉(zhuǎn)換。refined concept可以隱式地轉(zhuǎn)換成base concept,反過來必須顯式地進(jìn)行,并且通過concept_cast操作符執(zhí)行。兄弟concept之間也必須通過concept_cast轉(zhuǎn)換。
            5. 基于concept的重載,也可以在runtime時(shí)執(zhí)行,實(shí)現(xiàn)泛化的dynamic-dispatch操作。

                下面,就開始第一個(gè)案例。

            案例:升級(jí)的坦克

                假設(shè)我們做一個(gè)游戲,主題是開坦克打仗。按游戲的慣例,消滅敵人可以得到積分,積分到一定數(shù)量,便可以升級(jí)。為了簡(jiǎn)便起見,我們只考慮對(duì)主炮升級(jí)。第一級(jí)的主炮是90mm的;第二級(jí)的主炮升級(jí)到120mm。主炮分兩種,一種只能發(fā)射穿甲彈,另一種只能發(fā)射高爆彈。因此,坦克也分為兩種:能打穿甲彈的和能打高爆彈的。
                為了使代碼容易開發(fā)和維護(hù),我們考慮采用模塊化的方式:開發(fā)一個(gè)坦克的框架,然后通過更換不同的主炮,實(shí)現(xiàn)不同種類的坦克和升級(jí):
                  //一些基本的concept定義
                  //炮彈頭concept
                  concept Warheads<typename T> {
                      double explode(TargetType tt); //炮彈爆炸,返回殺傷率。不同彈頭,對(duì)不同類型目標(biāo)殺傷率不一樣。
                  }
                  //炮彈concept,我們關(guān)心的當(dāng)然是彈頭,所以用Warheads定義一個(gè)associate type
                  concept Rounds<typename T> {
                      Warheads WH;
                      ...
                  }
                  //主炮concept
                  concept Cannons<typename T> {
                      Rounds R;
                      void T::load(R& r); //裝填炮彈,load之后炮彈會(huì)存放在炮膛里,不能再load,除非把炮彈打出去
                      R::WH T::fire();   //開炮,返回彈頭。發(fā)射后炮膛變空,可以再load
                  }
                  //類型和模板定義
                  //坦克類模板
                  template<Cannons C>
                  class Tank
                  {
                      ...
                  public:
                      void load(typenam C::R& r) {
                          m_cannon.load(r);
                      }
                      typename C::R::WH fire() {
                          return m_cannon.fire();
                      }
                  private:
                      C m_cannon;
                  };
                  //主炮類模板
                  template<Rounds Rd>
                  class Cannon
                  {
                  public:
                      typedef Rd R;
                      void load(R& r) {...}
                      typename R::WH fire() {...}
                  }
                  template<Rounds Rd> concept_map Cannons<Cannon<Rd>>{}
                  //炮彈類模板
                  template<Warheads W>
                  class Round
                  {
                  public:
                      typedef W WH;
                      static const int caliber=W::caliber;
                      W shoot() {...}
                      ...
                  };
                  template<Warhead W> concept_map<Round<W>>{}
                  //彈頭類模板,通過traits把各類彈頭的不同行為彈頭的代碼框架分離,使類型“可組裝”
                  concept WH_Traits<T> {
                      return T::exploed(int, TargetType, double, Material);
                  }
                  template<WH_Traits wht, int c>
                  class Warhead
                  {
                  public:
                      const static int caliber=c;
                      double explode(TargetType tt) {
                          return wht::exploed(c, tt, ...);
                      }
                      ...
                  };
                  template<WH_Traits WHT, int c> concept_map<Warhead<WHT, c>>{}
                  //彈頭traits
                  struct KE_WHTraits
                  {
                      static double exploed(int caliber, TargetType tt, double weight, Material m) {...}
                  };
                  concept_map<KE_WHTraits>{}
                  struct HE_WHTraits
                  {
                      static double exploed(int caliber, TargetType tt, double weight, Material m) {...}
                  };
                  concept_map<HE_WHTraits>{}
                  //定義各類彈頭
                  typedef Warhead<KE_WHTraits, 90> WH_KE_90;
                  typedef Warhead<KE_WHTraits, 120> WH_KE_120;
                  typedef Warhead<HE_WHTraits, 90> WH_HE_90;
                  typedef Warhead<HE_WHTraits, 120> WH_HE_120;
                  //定義各類炮彈
                  typedef Round<WH_KE_90> Round_KE_90;
                  typedef Round<WH_KE_120> Round_KE_120;
                  typedef Round<WH_HE_90> Round_HE_90;
                  typedef Round<WH_HE_120> Round_HE_120;
                  //定義各類主炮
                  typedef Cannon<Round_KE_90> Cannon_KE_90;
                  typedef Cannon<Round_KE_120> Cannon_KE_120;
                  typedef Cannon<Round_HE_90> Cannon_HE_90;
                  typedef Cannon<Round_HE_120> Cannon_HE_120;
                  //定義各類坦克
                  typedef Tank<Cannon_KE_90> Tank_KE_90;
                  typedef Tank<Cannon_KE_120> Tank_KE_120;
                  typedef Tank<Cannon_HE_90> Tank_HE_90;
                  typedef Tank<Cannon_HE_120> Tank_HE_120;
                于是,當(dāng)我們開始游戲時(shí),就可以按照玩家的級(jí)別創(chuàng)建坦克對(duì)象,并且射擊:
                  //第一級(jí)玩家,駕駛發(fā)射90mm高爆炮彈的坦克
                  Tank_HE_90 myTank;
                  Round_HE_90 r1;
                  myTank.load(r1);
                  myTank.fire();
                  //第二級(jí)玩家,駕駛發(fā)射120mm穿甲彈的坦克
                  Tank_KE_120 yourTank;
                  Round_KE_120 r2;
                  yourTank.load(r2);
                  yourTank.fire();
                  //如果這樣,危險(xiǎn),炮彈不匹配,小心炸膛
                  myTank.load(r2); //error
                到目前為止,這些代碼僅僅展示了靜態(tài)的GP。concept在這里也只是起到了類型參數(shù)約束的作用。但是,在這些代碼中,我們可以明顯地看到,在運(yùn)用GP 的參數(shù)化類型特性之后,可以很容易地進(jìn)行組件化。對(duì)于一組具備類似行為和結(jié)構(gòu)特征的類型,我們可以通過模板的類型參數(shù),將差異部分抽取出來,獨(dú)立成所謂的 “traits”或者“policy”。并且通過traits或policy的組合構(gòu)成不同的產(chǎn)品。在某些復(fù)雜的情況下,traits和policy還可以進(jìn)一步通過traits或policy實(shí)現(xiàn)組件化。
                接下來,很自然地應(yīng)當(dāng)展開runtime GP的運(yùn)用了。
                一個(gè)游戲者是可以升級(jí)的,為了使得這種升級(jí)變得更加靈活,我們會(huì)很自然地使用Composite模式。現(xiàn)在,我們可以在Runtime concept的支援下實(shí)現(xiàn)GP版的Composite模式:
                  //坦克的concept
                  concept tanks<T> {
                      typename Round;
                      void T::load(Round&);
                      Round::WH T::fire();
                  }
                  concept_map tanks<Tank_KE_90>{}
                  concept_map tanks<Tank_HE_90>{}
                  concept_map tanks<Tank_KE_120>{}
                  concept_map tanks<Tank_HE_120>{}
                  //坦克構(gòu)造函數(shù)模板
                  template<tanks T>
                  T* CreateTank(WHType type, int level) { //WHType是一個(gè)枚舉表明炮彈種類
                      switch(level)
                      {
                      case 1:
                          if(type==wht_KE)
                              return new tanks<Tank_KE_90>;
                          else
                              return new tanks<Tank_HE_90>;
                      case 2:
                          if(type==wht_KE)
                              return new tanks<Tank_KE_120>;
                          else
                              return new tanks<Tank_HE_120>;
                      default:
                          throw error("no such tank.");
                      }
                  }
                  //玩家類
                  class player
                  {
                  public:
                      void update() {
            m_pTank=CreateTank(m_tankType, ++m_level);
                      }
                      ...
                  private:
                      int m_level;
                      WHType m_tankType;
            tanks* m_pTank;
                  };
                在類player中,使用了一個(gè)concept,而不是一個(gè)類型,來定義一個(gè)對(duì)象。根據(jù)前面提到的concept推導(dǎo)規(guī)則,m_pTank指向一個(gè)動(dòng)態(tài)對(duì)象,還是靜態(tài)對(duì)象,取決于為它賦值的表達(dá)式類型是concept還是具體類型。在update()函數(shù)中,可以看到,m_pTank通過表達(dá)式CreateTank(m_tankType, ++m_level)賦值。那么這個(gè)函數(shù)的返回類型,將決定m_pTank的類型。CreateTank()是一個(gè)模板,返回類型是模板參數(shù),并且是符合concept tanks的類型。關(guān)鍵在于代碼中的return new tanks<...>語(yǔ)句。前文已經(jīng)說過,這種形式是使用<...>中的類型創(chuàng)建一個(gè)符合tanks的動(dòng)態(tài)對(duì)象。所以,CreateTank()返回的是動(dòng)態(tài)對(duì)象。那么,m_pTank也將指向一個(gè)動(dòng)態(tài)對(duì)象。在運(yùn)行時(shí),當(dāng)玩家達(dá)到一定條件,便可以升級(jí)。update()成員函數(shù)將根據(jù)玩家的級(jí)別重新創(chuàng)建相應(yīng)的坦克對(duì)象,賦值到m_pTank中。
                這里,實(shí)際上是利用tanks這個(gè)concept描述,充當(dāng)類型的公有接口。它所具有的特性同動(dòng)多態(tài)的抽象基類是非常相似的。但是所不同的是,如同我在代碼中展現(xiàn)的那樣,concept作為接口,可以在任何時(shí)候定義,同類型綁定。而無需象抽象基類那樣,必須在類型定義之前定義。于是,這種非侵入式的接口相比抽象基類擁有更加靈活自由的特性。
                然而,事情還沒有完。在進(jìn)一步深化坦克案例后,我們還將發(fā)現(xiàn)runtime GP擁有更加有趣和重要的特性。
                坦克開炮為的是攻擊目標(biāo)。對(duì)目標(biāo)的毀傷情況直接關(guān)系到玩家的生存和得分。所以,我們必須對(duì)射擊后,目標(biāo)的損毀情況進(jìn)行計(jì)算。于是編寫了這樣一組函數(shù):
                  double LethalityEvaluate(Target& t, double hitRate, WH_KE_90& wh) {...}
                  double LethalityEvaluate(Target& t, double hitRate, WH_HE_90& wh) {...}
                  double LethalityEvaluate(Target& t, double hitRate, WH_KE_120& wh) {...}
                  double LethalityEvaluate(Target& t, double hitRate, WH_HE_120& wh) {...}
                Target是目標(biāo);hitRate是命中率,根據(jù)坦克和目標(biāo)的位置、射擊參數(shù)綜合計(jì)算獲得(如果想要更真實(shí),可以加上風(fēng)向、風(fēng)力、氣溫、濕度、海拔等等因素);wh就是發(fā)射出來的炮彈了。函數(shù)返回殺傷率。如此,我們便可以在射擊之后進(jìn)行評(píng)估了:
                  double l=LethalityEvaluate(house, hr, myTank.fire());
                 現(xiàn)在,游戲需要進(jìn)行一些擴(kuò)展,增加一個(gè)升級(jí),允許坦克升級(jí)到第三級(jí)。到了第三極,主炮的口徑就升到頭了,但可以升級(jí)功能,可以發(fā)射穿甲彈和高爆彈。這樣,我們就需要一個(gè)“兩用”的主炮類。但是,實(shí)際上并不需要直接做這么一個(gè)類,只需要用“兩用”的彈藥來實(shí)例化Cannon模板:
                  concept Warheads120<T> : Warheads<T> { //120mm炮彈頭concept
                      double LethalityEvaluate(Target& t, double hitRate, T& wh);
                  }
                  concept Rounds120<T> : Rounds<T> {}
                  concept_map Warheads120<WH_KE_120> {}  //120mm的穿甲彈屬于Warheads120
                  concept_map Warheads120<WH_HE_120> {}  //120mm的高爆彈屬于Warheads120
                  template<WH120 WH> concept_map Rounds120<Round<WH>> {} //所有彈頭是Warheads120的炮彈都是屬于Rounds120
                  typedef Canon<Rounds120> Cannon120; //用Rounds120實(shí)例化Cannon模板,得到“兩用”主炮
                一堆炫目的concept和concept_map之后,得到Rounds120,就是所謂的“兩用”彈藥。作為一個(gè)concept,它同兩種類型map 在一起,實(shí)際上就成了這兩個(gè)類型的接口。當(dāng)我們使用Rounds120實(shí)例化Cannon<>模板時(shí),也就創(chuàng)建了一個(gè)“兩用的主炮”(使用 Rounds120彈藥的主炮)。如果用這個(gè)Cannon120實(shí)例化Tank模板,那么就可以得到第三級(jí)坦克(裝上Cannon120主炮的坦克就是第三級(jí)):
                  typedef Tank<Cannon120> TankL3;
                于是,我們可以使用不同的120mm彈藥裝填主炮,并且發(fā)射相應(yīng)的炮彈:
                  TankL3 tank_l3;
                  Round_KE_120 ke_round;  //創(chuàng)建一枚穿甲彈
                  Round_HE_120 he_round;  //創(chuàng)建一枚高爆彈
                  tank_l3.load(ke_round);  //裝填穿甲彈
                  tank_l3.fire();               //發(fā)射穿甲彈
                  tank_l3.load(he_round);  //裝填高爆彈
                  tank_l3.fire();               //發(fā)射高爆彈
                現(xiàn)在,我們把注意力從消滅敵人,轉(zhuǎn)移到TankL3::load()的參數(shù)類型和TankL3::fire()的返回類型上。在一級(jí)和二級(jí)坦克(類型 Tank_KE_90等)上,load()成員的參數(shù)類型是Round_KE_90等具體的類型;而fire()的返回類型亦是如此。但TankL3是用 Cannon120實(shí)例化的,而Cannon120是用Rounds120這個(gè)concept實(shí)例化的。根據(jù)Tank<>模板的定義, load()成員的參數(shù)類型實(shí)際上是模板參數(shù)上的一個(gè)associate type。而這個(gè)associate type實(shí)際上就是Rounds120。這意味著load()實(shí)例化后的簽名是:void load(Rounds120& r)(這里暫且允許concept作為類型參數(shù)使用)。只要符合Rounds120的類型都可以作為實(shí)參傳遞給load()成員。同樣,fire()成員的返回類型來自于Round120上的associate type,也是個(gè)concept。因此,fire()實(shí)例化后的簽名是:Warheads120 fire()。
                接下來值得注意的是fire()成員。它返回類型是一個(gè)concept,那么返回的將是一個(gè)動(dòng)態(tài)對(duì)象。在運(yùn)行時(shí),它可能返回WH_KE_120的實(shí)例,也可能返回WH_HE_120的實(shí)例,取決于運(yùn)行時(shí)load()函數(shù)所裝填的炮彈類型。當(dāng)我們采用LethalityEvaluate()函數(shù)對(duì)該炮彈的殺傷情況進(jìn)行評(píng)估將會(huì)出現(xiàn)比較微妙的情況:
                  double x=LethalityEvaluate(hisTank, hr, tank_l3.fire());
                這時(shí)候,編譯器應(yīng)當(dāng)選擇哪個(gè)LethalityEvaluate()?由于tank_l3.fire()返回的是一個(gè)動(dòng)態(tài)對(duì)象,具體的類型編譯時(shí)不知道。實(shí)際上,在正宗的靜態(tài)語(yǔ)言中,這樣的調(diào)用根本無法通過編譯。當(dāng)然,編譯器可以通過runtime reflect獲得類型信息,然后在LethalityEvaluate()的重載中匹配正確的函數(shù)。然而,這種動(dòng)態(tài)語(yǔ)言做法會(huì)造成性能上的問題,為靜態(tài)語(yǔ)言所不屑。
                但是,在這里,在runtime concept的作用下,我們可以使這種調(diào)用成為靜態(tài)的、合法的,并且是高效的。請(qǐng)注意我在concept Warhead120的定義中加入了一個(gè)associate function:double LethalityEvaluate(Target& t, double hitRate, T& wh);。runtime concept會(huì)很忠實(shí)地將concept定義中的associate function構(gòu)造到一個(gè)函數(shù)指針表(我稱之為ctable)中。(詳細(xì)情況請(qǐng)看本文附錄和這篇文章的附錄)。因此,與tank_l3.fire()返回的動(dòng)態(tài)對(duì)象實(shí)際類型對(duì)應(yīng)的LethalityEvaluate()函數(shù)版本的指針正老老實(shí)實(shí)地躺在相應(yīng)的ctable里。所以,我們可以直接從動(dòng)態(tài)對(duì)象上獲得指向ctable的指針,并且找出相應(yīng)的LethalityEvaluate()函數(shù)指針,然后直接調(diào)用即可。比如:
                  tank_l3.load(ke_round);
                  double x=LethalityEvaluate(hisTank, hr, tank_l3.fire());
                在這些代碼的背后,ke_round通過load()裝填入主炮后,便搖身變成了一個(gè)動(dòng)態(tài)對(duì)象。編譯器會(huì)為它附加上指向ctable的指針,然后在調(diào)用 fire()的時(shí)候返回指向這個(gè)動(dòng)態(tài)對(duì)象的引用。此時(shí),編譯器發(fā)現(xiàn)這個(gè)動(dòng)態(tài)對(duì)象所對(duì)應(yīng)的Warhead120 concept上已經(jīng)定義了一個(gè)名為L(zhǎng)ethalityEvaluate()的associate function,并且簽名與當(dāng)前調(diào)用相符。于是,便可以直接找到ctable中LethalityEvaluate()對(duì)應(yīng)的那個(gè)函數(shù)指針,無所顧忌的調(diào)用。由于一個(gè)concept的associate function肯定是同實(shí)際類型匹配的函數(shù)版本。比如,對(duì)于WH_HE_120而言,它的associate function LethalityEvaluate()是版本:double LethalityEvaluate(Target& t, double hitRate, WH_HE_120& wh) {...}。其他版本的LethalityEvaluate()都無法滿足concept Warhead120施加在類型WH_HE_120上的約束。
                這個(gè)特性就使得runtime concept作為接口,相比動(dòng)多態(tài)的抽象接口,具有更大的靈活性。抽象接口只表達(dá)了類的成員,以及類本身的行為,無法表達(dá)類型同其他類型的互動(dòng)關(guān)系,或者說類型間的交互。而concept同時(shí)描述了成員函數(shù)和相關(guān)的自由函數(shù)(包括操作符),使得類型間的關(guān)系也可以通過接口直接獲得,無需再通過 reflect等間接的動(dòng)態(tài)手段。
                這一點(diǎn)在處理內(nèi)置類型(如int、float)、預(yù)置類型(某些庫(kù)中的類型)、第三方類型等不易或無法修改的類型有至關(guān)重要的作用。在OOP下,我們無法輸出一個(gè)“整數(shù)”,或許是short、或許是long,甚至是unsinged longlong。為此,我們要么把它們轉(zhuǎn)換成一個(gè)“最基礎(chǔ)類型”(C/C++的void*,或C#的Object*),然后運(yùn)用rtti信息進(jìn)行類型轉(zhuǎn)換,再做處理;要么使用variant這種類型包裝(就像COM中的那樣),然后為他們?nèi)娑x一套計(jì)算庫(kù)。但runtime concept不僅僅允許輸出“整數(shù)”這樣一個(gè)動(dòng)態(tài)對(duì)象,而且還將相關(guān)的各種操作附在動(dòng)態(tài)對(duì)象之上,使之無需借助rtti或者輔助類型也可進(jìn)行各類處理,就如同處理具體類型的對(duì)象那樣。
                但是,在這里我僅僅考察了針對(duì)一個(gè)類型的concept(暫且稱之為一元concept),還未涉及兩個(gè)和兩個(gè)以上類型的concept(暫且稱為多元 concept,或n-元concept)。在實(shí)際開發(fā)中,多數(shù)操作都會(huì)涉及多個(gè)對(duì)象,比如兩個(gè)數(shù)相加、一種類型轉(zhuǎn)換成另一種。此時(shí),我們將會(huì)面對(duì)多元的 concept。但是多元的runtime concept的特性還不清楚,還需要進(jìn)一步研究分析。

            總結(jié)

                本文初步展示了在引入runtime concept之后,GP的動(dòng)態(tài)化特性。歸納起來有以下幾點(diǎn):
            1. static GP和runtime GP之間在形式上完全統(tǒng)一,兩者可以看作同一種抽象機(jī)制的不同表現(xiàn)。因此,我們?cè)跇?gòu)造類型、函數(shù)等代碼實(shí)體的時(shí)候,并不需要考慮它們將來需要作為static使用,還是runtime使用。static和runtime的控制完全取決于這些代碼實(shí)體的使用方式。這就很好地減少了軟件項(xiàng)目早期設(shè)計(jì),以及庫(kù)設(shè)計(jì)的前瞻性方面壓力。
            2. runtime concept作為非侵入式的接口,可以非常靈活地使用。我們無需在代碼編寫的一開始就精確地定義好接口,可以先直接編寫功能類型,逐步構(gòu)建軟件結(jié)構(gòu)。需要時(shí)再定義接口(concept),并可以在任何時(shí)候與類型綁定。接口的定制可以成為一個(gè)逐步推進(jìn)的過程,早期的接口設(shè)計(jì)不足產(chǎn)生的不良影響相應(yīng)地弱化了。
            3. runtime concept相比動(dòng)多態(tài)的抽象接口更加自由。concept可以對(duì)類型的成員函數(shù)、自由函數(shù)、類型特征等等方面的特性作出描述。在runtime化之后,相關(guān)自由函數(shù)成為了接口的一部分。更進(jìn)一步規(guī)約了類型在整體軟件的代碼環(huán)境中的行為特征。同時(shí),也為動(dòng)態(tài)對(duì)象的訪問提供更多的信息和手段。
            4. concept不僅實(shí)現(xiàn)類型描述,還可以進(jìn)一步描述類型之間的關(guān)系。這大大完善了抽象體系。特別在runtime情況下,這種更寬泛的類型描述能力可以起到兩個(gè)作用:其一,進(jìn)一步約束了動(dòng)態(tài)對(duì)象的行為;其二,為外界操作和使用類型提供更多的信息,消除或減少了類型匹配方面的抽象懲罰。這個(gè)方面的更多特性尚不清楚,還需要更進(jìn)一步地深入研究。
                綜上所述,我們可以看到runtime GP在不損失性能的情況下,具備相比動(dòng)多態(tài)更靈活、更豐富的手段。從根本上而言,以concept為核心的GP提供了更基礎(chǔ)的抽象體系(關(guān)于這方面探討,請(qǐng)看我的這篇文章中關(guān)于concept對(duì)類型劃分的作用部分)。或者說,concept的類型描述和約束作用體現(xiàn)了類型抽象的本質(zhì),而在此基礎(chǔ)上進(jìn)一步衍生出static和runtime兩種具體的使用方式。這也就是所謂:道生一,一生二。:)

            附錄

            Runtime Concept實(shí)現(xiàn)方案二

                我在這篇文章附錄里,給出了一種實(shí)現(xiàn)runtime concept的可能方案。這里,我進(jìn)一步對(duì)這個(gè)方案做了一些改進(jìn),使其更加精簡(jiǎn)、高效。
                假設(shè)我們有一個(gè)concept:
                concept Shape<T>
                {
                    void T::load(xml);
                    void T::draw(device);
                    void move(T&);
                }
                另外,還有一個(gè)代表圓的concept:
                concept Cycles<T> :
                    CopyConstructable<T>,
                    Assignable<T>,
                    Swappable<T>,
                    Shape<T>
                {
                    T::T(double, double, double);
                    double T::getX();
                    double T::getY();
                    double T::getR();
                    void T::setX(double);
                    void T::setY(double);
                    void T::setR(double);
                }
                現(xiàn)在有類型Cycle:
                class Cycle
                {
                public:
                    Cycle(double x, double y, double r);
                    Cycle(Cycle const& c);
                    Cycle& operator=(Cycle const& c);
                    void swap(Cycle const& c);
                    void load(xml init);
                    void draw(device dev);
                    double getX();
                    double getY();
                    double getR();
                    void setX(double x);
                    void setY(double y);
                    void setR(double r);
                private:
                    ...
                };
                我們將類型Cycle map到concept Cycles上:
                  concept_map Cycles<Cycle>{}
                當(dāng)我們創(chuàng)建對(duì)象時(shí),將會(huì)得到如下圖的結(jié)構(gòu):

            runtime concept-2

                concept表(concept list)不再同對(duì)象放在一起,而是同一個(gè)類型的類型信息放在一起。一同放置的還有ctable。ctable中每個(gè)對(duì)應(yīng)的concept項(xiàng)都有一個(gè)指向 concept表的指針(也可以指向類型信息頭),用以找到concept list,執(zhí)行concept cast。動(dòng)態(tài)對(duì)象,或動(dòng)態(tài)對(duì)象的引用/指針上只需附加一個(gè)指向相應(yīng)的concept的指針即可。相比前一個(gè)方案,內(nèi)存利用率更高。

            posted @ 2008-01-06 17:17 longshanks 閱讀(3059) | 評(píng)論 (7)編輯 收藏

            2007年12月17日 #

            被誤解的C++——漢尼拔

            by 莫華楓

                公元前216年8月2日,意大利東部平原,一個(gè)叫做坎尼的地方,兩支大軍擺開陣勢(shì),準(zhǔn)備決一死戰(zhàn)。一方是由保羅斯和瓦羅兩位執(zhí)政官率領(lǐng)的羅馬人,另一方則是偉大的軍事天才漢尼拔*巴卡率領(lǐng)的迦太基軍隊(duì)及其同盟。羅馬人超過8萬,而迦太基僅有4萬余人。然而到了傍晚,羅馬人被徹底擊敗,7萬人被殺,僅有少數(shù)得以逃脫。這就是著名的坎尼會(huì)戰(zhàn)。經(jīng)此一役,(外加先前進(jìn)行的特利比亞和特拉西梅諾湖會(huì)戰(zhàn)),羅馬人元?dú)獯髠赡旯駬p失達(dá)五分之一。部分城邦背叛羅馬,西西里也發(fā)生起義。羅馬已經(jīng)到了搖搖欲墜的地步。

                漢尼拔的這些勝利,完全得益于先前的一次異乎尋常的遠(yuǎn)征。公元前218年,漢尼拔率領(lǐng)軍隊(duì),從新迦太基城(西班牙)出發(fā),翻越比利牛斯山,進(jìn)入南高盧地域。在他面前有兩條路可走,翻越阿爾俾斯山,或者沿海岸進(jìn)入意大利。但是,當(dāng)時(shí)羅馬人已在沿海地區(qū)部署了兩支部隊(duì),準(zhǔn)備攔截漢尼拔。而且,羅馬人的海軍優(yōu)勢(shì),使得他們可以在任何時(shí)候?qū)⒁恢Р筷?duì)登陸在他的背后。而翻越阿爾俾斯山,則是一條及其艱險(xiǎn)的道路,更何況是在冬天。

                漢尼拔選擇了阿爾俾斯山。他甩開了羅馬人,從小圣貝納德和日內(nèi)瓦山之間越過阿爾俾斯山,進(jìn)入意大利境內(nèi)。此時(shí),羅馬人便失去了戰(zhàn)略縱深,一把尖刀已經(jīng)深深地插入他們的腹內(nèi)...

             

                C++的發(fā)展史上,也有著如同漢尼拔翻越阿爾俾斯山遠(yuǎn)征。一切還得從C with Class時(shí)代說起。

                Bjarne曾經(jīng)反復(fù)強(qiáng)調(diào),他創(chuàng)建C++為的是將Simular的抽象能力同C的性能結(jié)合起來。于是,在C語(yǔ)言的基礎(chǔ)上,誕生了一種擁有類、繼承、重載等等面向?qū)ο髾C(jī)制的語(yǔ)言。在這個(gè)階段,C++提供了兩個(gè)方面的抽象能力。一種是數(shù)據(jù)抽象,也就是將數(shù)據(jù)所要表達(dá)的含義通過類型以及依附于類型上的成員表述。另一種則是多態(tài),一種最原始的多態(tài)(重載)。

                數(shù)據(jù)抽象,通過被稱為“抽象數(shù)據(jù)類型(ADT)”的技術(shù)實(shí)現(xiàn)。ADT的一種方案,就是類。類所提供的封裝性將一個(gè)數(shù)據(jù)實(shí)體的外在特征,或者說語(yǔ)義的表述形式,同具體的實(shí)現(xiàn),比如數(shù)據(jù)存儲(chǔ)形式,分離。這樣所增加的中間層將數(shù)據(jù)的使用者同數(shù)據(jù)的實(shí)現(xiàn)者隔離,使得他們使用共同的約定語(yǔ)義工作,不再相互了解彼此的細(xì)節(jié),從而使得兩者得以解耦。

                多態(tài)則是更加基礎(chǔ)更加重要的一種特性。多態(tài)使得我們得以用同一種符號(hào)實(shí)現(xiàn)某種確定的語(yǔ)義。多態(tài)的精髓在于:以一種形式表達(dá)一種語(yǔ)義。在此之前,我們往往被迫使用不同的符號(hào)來代表同一種抽象語(yǔ)義,為的是適應(yīng)強(qiáng)類型系統(tǒng)所施加的約束。比如:

            //代碼#1
            int add_int(int lhs, int rhs);
            float add_float(float lhs, float rhs);

                很顯然,這兩個(gè)函數(shù)表達(dá)的語(yǔ)義分別是“把兩個(gè)int類型值加在一起”和“把兩個(gè)float類型值加在一起”。這兩個(gè)語(yǔ)義抽象起來都表達(dá)了一個(gè)意思:加。

                我們?cè)谧鏊阈g(shù)題的時(shí)候是不會(huì)管被計(jì)算的數(shù)字是整數(shù)還是實(shí)數(shù)。同樣,如果能夠在編程的時(shí)候,不考慮算術(shù)操作對(duì)象的類型,只需關(guān)心誰(shuí)和誰(shuí)進(jìn)行什么操作,那么會(huì)方便得多。當(dāng)C++引入重載后,這種愿望便得以實(shí)現(xiàn):

            //代碼#2
            int add(int lhs, int rhs);
            float add(float lhs, float rhs);

                重載使得我們只需關(guān)心“加”這個(gè)語(yǔ)義,至于什么類型和什么類型相加,則由編譯器根據(jù)操作數(shù)的類型自動(dòng)解析。

                從某種意義上說,重載是被長(zhǎng)期忽視,但卻極為重要的一個(gè)語(yǔ)言特性。在多數(shù)介紹OOP的書籍中,重載往往被作為OOP的附屬品,放在一些不起眼的地方。它的多態(tài)本質(zhì)也被動(dòng)多態(tài)的人造光環(huán)所設(shè)遮蔽。然而,重載的重要作用卻在實(shí)踐中潛移默化地體現(xiàn)出來。重載差不多可以看作語(yǔ)言邁入現(xiàn)代抽象體系的第一步。它的實(shí)際效用甚至要超過被廣為關(guān)注的OOP,而不會(huì)像OOP那樣在獲得抽象的同時(shí),伴隨著不小的副作用。

                 隨著虛函數(shù)的引入,C++開始具備了頗具爭(zhēng)議的動(dòng)多態(tài)技術(shù)。虛函數(shù)是一種依附于類(OOP的類型基礎(chǔ))的多態(tài)技術(shù)。其技術(shù)基礎(chǔ)是后期綁定(late-binding)。當(dāng)一個(gè)類D繼承自類B時(shí),它有兩種方法覆蓋(override)B上的某個(gè)函數(shù):

            //代碼#3
            class B
            {
            public:
                void fun1();
                virtual void fun2();
            };

            class D:public B
            {
            public:
                void fun1();
                void fun2();
            };

                當(dāng)繼承類D中聲明了同基類B中成員函數(shù)相同函數(shù)名、相同簽名的成員函數(shù),那么基類的成員函數(shù)將被覆蓋。對(duì)于基類的非虛成員函數(shù),繼承類會(huì)直接將其遮蔽。對(duì)于類型D的使用者,fun1代表了D中所賦予的語(yǔ)義。而類型D的實(shí)例,可以隱式地轉(zhuǎn)換成類型B的引用b,此時(shí)調(diào)用b的fun1,則執(zhí)行的是類B的fun1 定義,而非類D的fun1,盡管此時(shí)b實(shí)際指向一個(gè)D的實(shí)例。

                但是,如果繼承類覆蓋了基類的虛函數(shù),那么將得到相反的結(jié)果:當(dāng)調(diào)用引用b的fun2,實(shí)際上卻是調(diào)用了D的fun2定義。這表明,覆蓋一個(gè)虛函數(shù),將會(huì)在繼承類和基類之間的所有層次上執(zhí)行覆蓋。這種徹底的、全方位的覆蓋行為,使得我們可以在繼承類上修飾或擴(kuò)展基類的功能或行為。這便是OOP擴(kuò)展機(jī)制的基礎(chǔ)。而這種技術(shù)被稱為動(dòng)多態(tài),意思是基類引用所表達(dá)的語(yǔ)義并非取決于基類本身,而是來源于它所指向的實(shí)際對(duì)象,因此它是“多態(tài)”的。因?yàn)橐粋€(gè)引用所指向的對(duì)象可以在運(yùn)行時(shí)變換,所以它是“動(dòng)”的。

                隨著動(dòng)多態(tài)而來的一個(gè)“副產(chǎn)品”,卻事實(shí)上成為了OOP的核心和支柱。虛函數(shù)的“動(dòng)多態(tài)”特性將我們引向一個(gè)極端的情況:一個(gè)都是虛函數(shù)的類。更重要的,這個(gè)類上的虛函數(shù)都沒有實(shí)現(xiàn),每個(gè)虛函數(shù)都未曾指向一個(gè)實(shí)實(shí)在在的函數(shù)體。當(dāng)然,這樣的類是無法直接使用的。有趣的是,這種被稱為“抽象基類”的類,迫使我們繼承它,并“替它”實(shí)現(xiàn)那些沒有實(shí)現(xiàn)的虛函數(shù)。這樣,對(duì)于一個(gè)抽象基類的引用,多態(tài)地?fù)碛辛死^承類的行為。而反過來,抽象基類實(shí)際上起到了強(qiáng)迫繼承類實(shí)現(xiàn)某些特定功能的作用。因此,抽象基類扮演了接口的角色。接口具有兩重作用:一、約束繼承類(實(shí)現(xiàn)者)迫使其實(shí)現(xiàn)預(yù)定的成員函數(shù)(功能和行為);二、描述了繼承類必定擁有的成員函數(shù)(功能和行為)。這兩種作用促使接口成為了OOP設(shè)計(jì)體系的支柱。

                C++在這方面的進(jìn)步,使其成為一個(gè)真正意義上具備現(xiàn)代抽象能力的語(yǔ)言。然而,這種進(jìn)步并非“翻越阿爾俾斯山”。充其量也只能算作“翻越比利牛斯山”。對(duì)于C++而言,真正艱苦的遠(yuǎn)征才剛開始,那令人生畏的“阿爾俾斯山”仍在遙遠(yuǎn)的前方。

                同漢尼拔一樣,當(dāng)C++一腳邁入“現(xiàn)代抽象語(yǔ)言俱樂部”后,便面臨兩種選擇。或者在原有基礎(chǔ)上修修補(bǔ)補(bǔ),成為一種OOP語(yǔ)言;或者繼續(xù)前進(jìn),翻越那座險(xiǎn)峻的山峰。C++的漢尼拔——Bjarne Stroustrup——選擇了后者。

                從D&E的描述中我們可以看到,在C++的原始設(shè)計(jì)中就已經(jīng)考慮“類型參數(shù)”的問題。但直到90年代初,才真正意義上地實(shí)現(xiàn)了模板。然而,模板只是第一步。諸如Ada等語(yǔ)言中都有類似的機(jī)制(泛型,generic),但并未對(duì)當(dāng)時(shí)的編程技術(shù)產(chǎn)生根本性的影響。

                關(guān)鍵性的成果來源于Alex Stepanov的貢獻(xiàn)。Stepanov在后來被稱為stl的算法-容器庫(kù)上所做的努力,使得一種新興的編程技術(shù)——泛型編程(Generic Programming,GP)——進(jìn)入了人們的視野。stl的產(chǎn)生對(duì)C++的模板機(jī)制產(chǎn)生了極其重要的影響,促使了模板特化的誕生。模板特化表面上是模板的輔助特性,但是實(shí)際上它卻是比“類型參數(shù)”更加本質(zhì)的機(jī)能。

                假設(shè)我們有一組函數(shù)執(zhí)行比較兩個(gè)對(duì)象大小的操作:

            //代碼#4
            int compare(int lhs, int rhs);
            int compare(float lhs, float rhs);
            int compare(string lhs, string rhs);

                重載使得我們可以僅用compare一個(gè)函數(shù)名執(zhí)行不同類型的比較操作。但是這些函數(shù)具有一樣的實(shí)現(xiàn)代碼。模板的引入,使得我們可以消除這種重復(fù)代碼:

            //代碼#5
            template<typename T> int compare(T lhs, T rhs) {
                if(lhs==rhs)
                    return 0;
                if(lhs>rhs)
                    return 1;
                if(lhs<rhs)
                    return -1;
            }

                這樣一個(gè)模板可以應(yīng)用于任何類型,不但用一個(gè)符號(hào)表達(dá)了一個(gè)語(yǔ)義,而且用一個(gè)實(shí)現(xiàn)代替了諸多重復(fù)代碼。這便是GP的基本作用。

                接下來的變化,可以算作真正意義上的“登山”了。

                如果有兩個(gè)指針,分別指向兩個(gè)相同類型的對(duì)象。此時(shí)如果我們采用上述compare函數(shù)模板,那么將無法得到所需的結(jié)果。因?yàn)榇藭r(shí)比較的是兩個(gè)指針的值,而不是所指向的對(duì)象本身。為了應(yīng)付這種特殊情況,我們需要對(duì)compare做“特別處理”:

            //代碼#6
            template<typename T> int compare(T* lhs, T* rhs) {
                if(*lhs==*rhs)
                    return 0;
                if(*lhs>*rhs)
                    return 1;
                if(*lhs<*rhs)
                    return -1;
            }

                這個(gè)“特殊版本”的compare,對(duì)于任何類型的指針作出響應(yīng)。如果調(diào)用時(shí)的實(shí)參是一個(gè)指針,那么這個(gè)“指針版”的compare將會(huì)得到優(yōu)先匹配。如果我們將compare改成下面的實(shí)現(xiàn),那么就會(huì)出現(xiàn)非常有趣的行為:

            //代碼#7
            template<typename T>
            struct comp_impl
            {
                int operator()(T lhs, T rhs) {
                    if(lhs==rhs)
                        return 0;
                    if(lhs>rhs)
                        return 1;
                    if(lhs<rhs)
                        return -1;
                }
            };
            template<typename T>
            struct comp_impl<T*>
            {
                int operator()(T* lhs, T* rhs) {
                    comp_impl<T>()(*lhs, *rhs);
                }
            };
            template<typename T> int compare(T* lhs, T* rhs) {
                comp_impl<T>()(*lhs, *rhs);
            }

                當(dāng)我們將指針的指針作為實(shí)參,調(diào)用compare時(shí),神奇的事情發(fā)生了:

            //代碼#8
            double **x, **y;
            compare(x, y);

                compare居然成功地剝離了兩個(gè)指針,并且正確地比較了兩個(gè)對(duì)象的值。這個(gè)戲法充分利用了類模板的局部特化和特化解析規(guī)則。根據(jù)規(guī)則,越是特化的模板,越是優(yōu)先匹配。T*版的comp_impl比T版的更加“特化”,會(huì)得到優(yōu)先匹配。那么當(dāng)一個(gè)指針的指針實(shí)例化comp_impl,則會(huì)匹配T*版的 comp_impl,因?yàn)橹羔樀闹羔槪彩侵羔槨*版通過局部特化機(jī)制,剝離掉一級(jí)指針,然后用所得的類型實(shí)例化comp_impl。指針的指針剝離掉一級(jí)指針,那么還是一個(gè)指針,又會(huì)匹配T*版。T*版又會(huì)剝離掉一級(jí)指針,剩下的就是真正可以比較的類型——double。此時(shí),double已無法與 T*版本匹配,只能匹配基礎(chǔ)模板,執(zhí)行真正的比較操作。

                這種奇妙的手法是蘊(yùn)含在模板特化中一些更加本質(zhì)的機(jī)制的結(jié)果。這種意外獲得的“模板衍生產(chǎn)品”可以算作一種編譯時(shí)計(jì)算的能力,后來被一些“好事者”發(fā)展成獨(dú)立的“模板元編程”(Template Meta Programming,TMP)。

                盡管TMP新奇而又奧妙,但終究只是一種輔助技術(shù),用來彌補(bǔ)C++的一些缺陷、做一些擴(kuò)展,“撿個(gè)漏”什么的。不過它為我們帶來了兩點(diǎn)重要的啟示:一、我們有可能通過語(yǔ)言本身的一些機(jī)制,進(jìn)行元編程;二、元編程在一定程度上可以同通用語(yǔ)言一起使用。這些啟示對(duì)編程語(yǔ)言的發(fā)展有很好的指導(dǎo)意義。

                模板及特化規(guī)則是C++ GP的核心所在。這些語(yǔ)言特性的強(qiáng)大能力并非憑空而來。實(shí)際上有一只“幕后大手”在冥冥之中操縱著一切。

                假設(shè)有一個(gè)類型系統(tǒng),包含n個(gè)類型:t1,...,tn,那么這些類型構(gòu)成了一個(gè)集合T={t1,...,tn}。在當(dāng)我們運(yùn)用重載技術(shù)時(shí),實(shí)際上構(gòu)造了一組類型的tuple到函數(shù)實(shí)現(xiàn)的映射:<ti1,ti2,ti3,...> ->fj()。編譯器在重載解析的時(shí)候,就是按照這組映射尋找匹配的函數(shù)版本。當(dāng)我們編寫了形如代碼#5的模板,那么就相當(dāng)于構(gòu)建了映射:<T, T,...> ->f0()。

                而代碼#6,以及代碼#7中的T*版模板,實(shí)際上是構(gòu)造了一個(gè)<Tp>->fp()的映射。這里Tp是T的一個(gè)子集:Tp={t'|t'=ti*, ti∈T}。換句話說,特化使泛型體系細(xì)化了。利用模板特化技術(shù),我們已經(jīng)能夠(笨拙地)分辨浮點(diǎn)數(shù)、整數(shù)、內(nèi)置類型、內(nèi)置數(shù)組、類、枚舉等等類型。具備為類型劃分的能力,也就是構(gòu)造不同的類型子集的能力。

                現(xiàn)在,我們便可以構(gòu)造一個(gè)“泛型體系”:G={T} U T U Tp U Ta U Ti U Tf U Tc ...。其中,Tp是所有指針類型,Ta是數(shù)組,Ti是整數(shù),Tf是浮點(diǎn)數(shù),Tc是類等等。但是如果我們按照泛化程度,把G中的元素排列開:{T, Tp, Ta, Ti,...,t1,...,tn}。我們會(huì)發(fā)現(xiàn)這中間存在一些“斷層”。這些斷層位于T和Tp等之間,以及Tp等與ti等之間等等。這表明在C++98/03中,抽象體系不夠完整,存在缺陷。

                所以,到目前為止,C++還沒有真正翻越阿爾俾斯山里那座最險(xiǎn)峻的山峰。這正是C++0x正在努力做的,而且勝利在望。

                在C++0x中,大牛們引入了first-class的concept支持。concept目前還沒有正式的法定描述(以及合理的中文翻譯)。通俗地講,concept描述了一個(gè)類型的(接口)特征。說具體的,一個(gè)concept描述了類型必須具備的公共成員函數(shù),必須具備的施加在該類型上的自由函數(shù)(和操作符),以及必須具備的其他特征(通過間接手段)。下面是一個(gè)典型的concept:

            concept has_equal<T>
            {
                bool T::equal(T const& v);
            };

                這個(gè)concept告訴我們它所描述的類型必須有一個(gè)equal成員,以另一個(gè)同類型的對(duì)象為參數(shù)。當(dāng)我們將這個(gè)concept施加在一個(gè)函數(shù)模板上,并作為對(duì)類型參數(shù)的約束,那么就表明了這個(gè)模板對(duì)類型參數(shù)的要求:

            template<has_equal T>bool is_equal(T& lhs, T const& rhs) {
                return lhs.equal(rhs);
            }

                如果實(shí)參對(duì)象的類型沒有equal成員,那么is_equal將會(huì)拒絕編譯通過:這不是我要的!

                concept是可以組合的,正式的術(shù)語(yǔ)叫做“refine”。我們可以通過refine進(jìn)一步構(gòu)造出約束更強(qiáng)的concept:

            concept my_concept<T> : has_equal<T>, DefaultConstructable<T>, Swappable<T> {}

                refine獲得的concept將會(huì)“繼承”那些“基concept”的所有約束。作為更細(xì)致的組合手段,concept還可以通過!操作符“去掉”某些內(nèi)涵的concept約束:

            concept my_concept1<T> : has_equal<T>, !DefaultConstructable<T> {}

                這個(gè)concept要求類型具備equal成員,但不能有默認(rèn)構(gòu)造函數(shù)。

                通過這些手段,concept可以“無限細(xì)分”類型集合T。理論上,我們可以制造一連串只相差一個(gè)函數(shù)或者只相差一個(gè)參數(shù)的concept。

                一個(gè)concept實(shí)際上就是構(gòu)成類型集合T的劃分的約束:Tx={ti| Cx(ti)==true, ti∈T}。其中Cx就是concept所構(gòu)造的約束。不同的concept有著不同范圍的約束。這樣,理論上我們可以運(yùn)用concept枚舉出類型集合 T的所有子集。而這些子集則正好填補(bǔ)了上述G中的那些斷層。換句話說,concept細(xì)化了類型劃分的粒度,或者說泛型的粒度。使得“離散”的泛型系統(tǒng)變成“連續(xù)”的。

                當(dāng)我們運(yùn)用concept約束一個(gè)函數(shù)模板的類型參數(shù)時(shí),相當(dāng)于用concept所描述的類型子集構(gòu)建一個(gè)映射:<Tx1,Tx2,...>->fx()。凡是符合tuple <Tx1,Tx2,...>的類型組合,對(duì)應(yīng)fx()。所以,從這個(gè)角度而言,函數(shù)模板的特化(包括concept)可以看作函數(shù)重載的一種擴(kuò)展。在concept的促進(jìn)下,我們便可以把函數(shù)模板特化和函數(shù)重載統(tǒng)一在一個(gè)體系下處理,使用共同的規(guī)則解析。

                在目前階段,C++差不多已經(jīng)登上了“抽象阿爾俾斯山”的頂峰。但是就如同漢尼拔進(jìn)入意大利后,還需要面對(duì)強(qiáng)盛的羅馬共和國(guó),與之作戰(zhàn)那樣。C++的面前還需要進(jìn)一步將優(yōu)勢(shì)化作勝利。要做的事還很多,其中最重要的,當(dāng)屬構(gòu)建Runtime GP。目前C++的GP是編譯時(shí)機(jī)制。對(duì)于運(yùn)行時(shí)決斷的任務(wù),還需要求助于OOP的動(dòng)多態(tài)。但是C++領(lǐng)域的大牛們已經(jīng)著手在Runtime GP和Runtime Concept等方面展開努力。這方面的最新成果可以看這里這里這里。相信經(jīng)過若干年的努力后,GP將會(huì)完全的成熟,成為真正主流的編程技術(shù)。

             

                坎尼會(huì)戰(zhàn)之后,漢尼拔已經(jīng)擁有了絕對(duì)的優(yōu)勢(shì)。羅馬人已經(jīng)戰(zhàn)敗,他同羅馬城之間已經(jīng)沒有任何強(qiáng)大的敵對(duì)力量,羅馬人也已經(jīng)聞風(fēng)喪膽,幾無斗志。但是,漢尼拔卻犯下了或許令他一生后悔的錯(cuò)誤。他放過了羅馬城,轉(zhuǎn)而攻擊羅馬的南部城邦和同盟。他低估了羅馬人的意志,以及羅馬同盟的牢固程度。羅馬人很快結(jié)束了最初的混亂,任命了新的執(zhí)政官,采用了堅(jiān)壁清野、以柔克剛的新戰(zhàn)略。隨著時(shí)間的推移,漢尼拔和他的軍隊(duì)限于孤立無援的境地,被迫為了生存而作戰(zhàn)。盡管迫降并占領(lǐng)了幾個(gè)羅馬城市,但是終究無法再次獲得給予羅馬人致命一擊的機(jī)會(huì)。

                漢尼拔的戰(zhàn)略錯(cuò)誤實(shí)際上在從新迦太基城出發(fā)之前已經(jīng)注定。因?yàn)闈h尼拔對(duì)羅馬人的遠(yuǎn)征的根本目的并非擊潰并占領(lǐng)羅馬,而是通過打擊羅馬,削弱他們的勢(shì)力,瓦解他們的聯(lián)盟。以達(dá)到尋求簽訂和平協(xié)議的目的。然而這種有限戰(zhàn)略卻使導(dǎo)致了他的最終失敗。

             

                不幸的是,C++或多或少地有著同漢尼拔一樣的戰(zhàn)略錯(cuò)誤。C++最初的目的基本上僅僅局限于“更好的C”。并且全面兼容C。這在當(dāng)時(shí)似乎很合理,因?yàn)镃可以算作最成功的“底層高級(jí)語(yǔ)言”,擁有很高的性能和靈活性。但是,C的設(shè)計(jì)并未考慮將來會(huì)有一個(gè)叫做“C++”的語(yǔ)言來對(duì)其進(jìn)行擴(kuò)展。結(jié)果很多優(yōu)點(diǎn)對(duì)于C而言是優(yōu)點(diǎn),但卻成了C++的負(fù)擔(dān)。比如,C大量使用操作符表達(dá)語(yǔ)法結(jié)構(gòu),對(duì)于C而言顯得非常簡(jiǎn)潔,但對(duì)于C++,則使其被迫大規(guī)模復(fù)用操作符,為其后出現(xiàn)的很多語(yǔ)法缺陷埋下了伏筆。這一點(diǎn)上,Ada做得相對(duì)成熟些。它從Pascal那里繼承了主要語(yǔ)法,但不考慮兼容。這使得Ada更加完整,易于發(fā)展。新語(yǔ)言就是新語(yǔ)言,過分的兼容是鐐銬,不是優(yōu)勢(shì)。而且,合理地繼承語(yǔ)法,同樣可以吸引眾多開發(fā)者。從經(jīng)驗(yàn)來看,程序員對(duì)于語(yǔ)法變化的承受能力還是很強(qiáng)的。他們更多地關(guān)心語(yǔ)言的功能和易用性。

                另一方面,C++最初并未把目標(biāo)定在“創(chuàng)建一種高度抽象,又確保性能的語(yǔ)言”。縱觀C++的發(fā)展,各種抽象機(jī)制并非在完整的規(guī)劃或路線圖的指導(dǎo)下加入語(yǔ)言。所有高級(jí)特性都是以“添油戰(zhàn)術(shù)”零打碎敲地加入語(yǔ)言。從某種程度上來看,C++更像是一種實(shí)驗(yàn)性語(yǔ)言,而非工業(yè)語(yǔ)言。C++的強(qiáng)大功能和優(yōu)點(diǎn)是長(zhǎng)期積累獲得的,而它的諸多缺陷也是長(zhǎng)期添加特性的結(jié)果。

                漢尼拔和C++給予我們一個(gè)很好的教訓(xùn)。對(duì)于一個(gè)試圖在1、20年后依然健康成長(zhǎng)的語(yǔ)言,那么就必須在最初便擁有明確的目標(biāo)和技術(shù)發(fā)展規(guī)劃。對(duì)于以往的語(yǔ)言特性應(yīng)當(dāng)擇優(yōu)而取,不能照單全收。并且在技術(shù)上擁有足夠的前瞻性。我們知道,技術(shù)前瞻性是很難做到的,畢竟技術(shù)發(fā)展太快。如果做不到,那就得有足夠的魄力對(duì)過去的東西加以取舍。所謂“舍小就大,棄子爭(zhēng)先”。

                總體而言,C++在抽象機(jī)制的發(fā)展方面,還算是成功的。盡管伴隨著不少技術(shù)缺陷,但是C++的抽象能力在各種語(yǔ)言中可稱得上出類拔萃。而且C++還在發(fā)展,它未來將會(huì)發(fā)展成什么形態(tài),不得而知。但是,無論C++是繼續(xù)修修補(bǔ)補(bǔ),還是根本性地變革,它的抽象能力都會(huì)不折不扣地保留,并且不斷完善和增強(qiáng)。

             

                坎尼會(huì)戰(zhàn)之后,漢尼拔又打過幾次小規(guī)模的勝仗。但經(jīng)過長(zhǎng)期的作戰(zhàn),也得不到迦太基的支援,漢尼拔的力量越來越弱,只能在意大利半島上勉強(qiáng)生存。羅馬很快恢復(fù)了元?dú)猓母锪塑娛麦w系和作戰(zhàn)方式,重新掌握了戰(zhàn)略主動(dòng)權(quán)。更重要的是,羅馬也有了自己的“漢尼拔”——(征服非洲的)普布利烏斯·科爾內(nèi)利烏斯·西庇阿(大西庇阿)。西庇阿被派往北非大陸,直接攻擊迦太基人的老巢。漢尼拔被召回,在扎馬與西庇阿擺開陣勢(shì),展開一場(chǎng)決戰(zhàn)。最終,西庇阿運(yùn)用從漢尼拔那里學(xué)到的戰(zhàn)術(shù)擊潰了迦太基人,為羅馬人贏得了第二次布匿戰(zhàn)爭(zhēng)的勝利。

                此后,漢尼拔在羅馬人的通緝之下,流亡于地中海沿岸,試圖尋求東山再起的機(jī)會(huì)。但最終未能如愿,被迫于公元前183年自盡,享年64歲。有趣的是,他的老對(duì)手,小他12歲的西庇阿也于同年去世。一個(gè)偉大的傳奇就此結(jié)束。

            posted @ 2007-12-17 11:28 longshanks 閱讀(2304) | 評(píng)論 (11)編輯 收藏

            2007年12月6日 #

                本文來源于TopLanguage Group 上的一次討論(這里這里這里 )。pongba提出:C++的抽象機(jī)制并不完善,原因是為了性能而做的折中,未來隨著計(jì)算能力的提高到一定程度,人們就能夠忽略更好的抽象所帶來的負(fù)面效應(yīng)。就此諸老大各自提出高見,受益良多啊。經(jīng)過討論,我基本上理解了pongba的想法。但我覺得等待計(jì)算機(jī)的性能提高太消極了。我相信隨著編程技術(shù)的發(fā)展,這種最優(yōu)抽象造成的性能損失將會(huì)越來越小。這種途徑將會(huì)更快地讓人們接受最優(yōu)抽象形式。

                 在“C++ Template”一書中,將多態(tài)總結(jié)為三種主要類型:runtime bound、static unbound和runtime unbound。其中runtime bound就是我們通常所說的動(dòng)多態(tài),OOP的核心支柱(廣義上OOP還包括Object Base(OB,僅指類型封裝等OO的基本特性),但有時(shí)也會(huì)將OB和OOP分開,OOP單指以O(shè)O為基礎(chǔ)的動(dòng)多態(tài)。這里使用狹義的OOP含義); static unbound就是靜多態(tài),通過模板實(shí)現(xiàn)。而runtime unbound則是一種不常見的形式。早年的SmallTalk具有這種形式,現(xiàn)在的ruby也引入這種機(jī)制。
                 在主流的(靜態(tài))語(yǔ)言中,我們會(huì)面臨兩種類型的多態(tài)需求:對(duì)于編譯期可以確定類型的,使用靜多態(tài),比如實(shí)例化一個(gè)容器;對(duì)于運(yùn)行期方能確定類型的,則使用 動(dòng)多態(tài)。而runtime unbound也可以用于運(yùn)行期類型決斷。于是,便有了兩種運(yùn)行期多態(tài)。這兩種多態(tài)的特性和他們的差異,是本文的核心。實(shí)際上,相比動(dòng)多態(tài), runtime unbound多態(tài)為我們提供了更本質(zhì)的運(yùn)行時(shí)多態(tài)手段,我們可以從中獲得更大的收益。但是鑒于一些技術(shù)上的困難,runtime unbound多態(tài)無法進(jìn)入主流世界。不過,由于新的編程技術(shù)的出現(xiàn),使得這種更好的運(yùn)行時(shí)多態(tài)形式可以同動(dòng)多態(tài)一比高下。

            動(dòng)多態(tài)   

                廢話少說,讓我們從一個(gè)老掉牙的案例開始吧:編寫一個(gè)繪圖程序,圖形包括矩形、橢圓、三角形、多邊形等等。圖形從腳本(比如xml)中讀出,創(chuàng)建后保存在一個(gè)容器中備查。通過遍歷容器執(zhí)行圖形繪制。
                就這么個(gè)題目,很簡(jiǎn)單,也很熟悉,解釋OOP的動(dòng)多態(tài)最常用的案例。下面我們就從動(dòng)多態(tài)實(shí)現(xiàn)開始。
                首先定義一個(gè)抽象基類,也就是接口:

                class IShape

                {

                    virtual void load(xml init)=0;

                    virtual void draw(monitor m)=0;

                    ...

                };

                然后定義各種圖形類,并從這個(gè)接口上繼承:

                class Rectangle: public IShape

                {

                    void load(xml init) {...}

                    void draw(monitor m) {...}

                    ...

                };

                class Ellipse: public IShape

                {

                    void load(xml init) {...}

                    void draw(monitor m) {...}

                    ...

                };

                ...

             

                void DrawShapes(monitor m, vector<IShape*> const& g)

                {

                    vector<IShape*>::const_iterator b(g.begin()), e(g.end());

                    for(; b!=e; ++b)

                    {

                        (*b)->draw(m);

                    }

                }

                ...

                現(xiàn)在可以使用這些圖形類了:

                vector<IShape*> vg;

                vg.push_back(new Rectangle);

                vg.push_back(new Ellipse);

                ...

                DrawShapes(crt, vg);

                通過接口IShape,我們可以把不同的圖形類統(tǒng)一到一種類型下。但是,通過虛函數(shù)的override,由圖形類實(shí)現(xiàn)IShape上的虛函數(shù)。這可以算老 生常談了。動(dòng)多態(tài)的核心就是利用override和late bound的組合,使得一個(gè)基類可以在類型歸一化的情況下,擁有繼承類的語(yǔ)義。OOP設(shè)計(jì)模式大量運(yùn)用這種技術(shù),實(shí)現(xiàn)很多需要靈活擴(kuò)展的系統(tǒng)。

            Runtime Unbound

                Runtime Unbound多態(tài)混合了靜多態(tài)和動(dòng)多態(tài)的特征,即既有類型泛化,又是運(yùn)行時(shí)決斷的。一個(gè)最典型的例子就是ruby的函數(shù):
                class x
                   def fun(car)
                        car.aboard
                    end
                end
                這個(gè)案例非常明確地展示出了Runtime Unbound多態(tài)的特點(diǎn)。car參數(shù)沒有類型,這里也不需要關(guān)心類型,只要求car對(duì)象有一個(gè)aboard方法即可。由于ruby是動(dòng)態(tài)語(yǔ)言,能夠運(yùn)行時(shí)檢測(cè)對(duì)象的特征,并動(dòng)態(tài)調(diào)用對(duì)象上的方法。
                在Runtime Unbound的思想指導(dǎo)下,我們利用一種偽造的“動(dòng)態(tài)C++”,把上面的繪圖例子重新編寫:

                class Rectangle

                {

                    void load(xml init) {...}

                    void draw(monitor dev) {...}

                    ...

                };

                class Ellipse

                {

                    void load(xml init) {...}

                    void draw(monitor dev) {...}

                    ...

                };

                ...

                void DrawShapes(monitor dev, vector<anything> const& g)

                {

                    vector<IShape>::const_iterator b(g.begin()), e(g.end());

                    for(; b!=e; ++b)

                    {

                        (*b).draw(dev);

                    }

                }

                ...

                vector<anything> vg;

                vg.push_back(Rectangle(...));

                vg.push_back(Ellipse(...));

                ...

                DrawShapes(crt, vg);

                圖形類不再?gòu)某橄蠼涌贗Shape繼承,而用關(guān)鍵字anything實(shí)例化vector<>模板。這個(gè)虛構(gòu)的anything關(guān)鍵字所起的作 用就是使得vector能夠接受不同類型的對(duì)象。當(dāng)DrawShapes()函數(shù)接收到存放圖形對(duì)象的容器后,遍歷每一個(gè)對(duì)象,并且調(diào)用對(duì)象上的draw ()函數(shù),而不管其類型。
                從這段代碼中,我們可以看出Runtime Unbound多態(tài)帶來的好處。所有圖形類不再需要?dú)w一化成一個(gè)類型(抽象接口)。每個(gè)類只需按照約定,實(shí)現(xiàn)load、draw等成員函數(shù)即可。也就是 說,這些圖形類解耦合了。一旦類型解耦,便賦予我們很大的自由度。最典型的情況就是,我們需要使用一個(gè)其他人開發(fā)的圖形類,并且無法修改其實(shí)現(xiàn)。此時(shí),如 果使用動(dòng)多態(tài),就很麻煩。因?yàn)楸M管這些圖形類都擁有l(wèi)oad、draw等函數(shù),但畢竟不是繼承自IShape,無法直接插入容器。必須編寫一個(gè)繼承自 IShape的適配器,作為外來圖形類的包裝,轉(zhuǎn)發(fā)對(duì)其的訪問。表面上,我們只是減少一個(gè)接口的定義,但Runtime Unbound多態(tài)帶來的解耦有著非凡的意義。因?yàn)轭愸詈鲜冀K是OOP設(shè)計(jì)中的一個(gè)令人頭痛的問題。在后面,我們還將看到建立在Runtime Unbound多態(tài)基礎(chǔ)上的更大的進(jìn)步。
                然而,盡管Runtime Unbound多態(tài)具有這些優(yōu)點(diǎn),但因?yàn)榻⒃趧?dòng)態(tài)語(yǔ)言之上,其自身存在的一些缺陷使得這項(xiàng)技術(shù)無法廣泛使用,并進(jìn)入主流。
                Runtime Unbound多態(tài)面臨的第一個(gè)問題就是類型安全。確切的講是靜態(tài)類型安全。
                本質(zhì)上,Runtime Unbound多態(tài)(動(dòng)態(tài)語(yǔ)言)并非沒有類型安全。當(dāng)動(dòng)態(tài)語(yǔ)言試圖訪問一個(gè)未知類型對(duì)象的成員時(shí),會(huì)通過一些特殊機(jī)制或特殊接口獲得類型信息,并在其中尋 找所需的對(duì)象成員。如果沒有找到,便會(huì)拋出異常。但是,傳統(tǒng)上,我們希望語(yǔ)言能夠在編譯期得到類型安全保證,而不要在運(yùn)行時(shí)才發(fā)現(xiàn)問題。也就是說, Runtime Unbound多態(tài)只能提供運(yùn)行時(shí)類型安全,而無法得到靜態(tài)類型安全。
                第二個(gè)問題是性能。Runtime Unbound需要在運(yùn)行時(shí)搜尋類型的接口,并執(zhí)行調(diào)用。執(zhí)行這類尋找和調(diào)用的方法有兩種:反射和動(dòng)態(tài)鏈接。
                反射機(jī)制可以向程序提供類型的信息。通過這些信息,Runtime Unbound可以了解是否存在所需的接口函數(shù)。反射通常也提供了接口函數(shù)調(diào)用的服務(wù),允許將參數(shù)打包,并通過函數(shù)名調(diào)用。這種機(jī)制性能很差,基本上無法用于稍許密集些的操作。
                動(dòng)態(tài)鏈接則是在訪問對(duì)象前在對(duì)象的成員函數(shù)表上查詢并獲得相應(yīng)函數(shù)的地址,填充到調(diào)用方的調(diào)用表中,調(diào)用方通過調(diào)用表執(zhí)行間接調(diào)用。這種機(jī)制相對(duì)快一些,但由于需要查詢成員函數(shù)表,復(fù)雜度基本上都在O(n)左右,無法與動(dòng)多態(tài)的O(1)調(diào)用相比。
                這些問題的解決,依賴于一種新興的技術(shù),即concept。concept不僅很消除了類型安全的問題,更主要的是它大幅縮小了兩種Runtime多態(tài)的性能差距,有望使Runtime Unbound成為主流的技術(shù)。

            concept

                隨著C++0x逐漸浮出水面,concept作為此次標(biāo)準(zhǔn)更新的核心部分,已經(jīng)在C++社群中引起關(guān)注。隨著時(shí)間的推移,concept的潛在作用也在不斷被發(fā)掘出來。
                concept主要用來描述一個(gè)類型的接口和特征。通俗地講,concept描述了一組具備了共同接口的類型。在引入concept后,C++可以對(duì)模板參數(shù)進(jìn)行約束:
                concept assignable<T> {
                    T& operator=(T const&);
                }
                template<assignable T> void copy(T& a, T const& b) {
                    a=b;
                }
                這表示類型T必須有operator=的重載。如果一個(gè)類型X沒有對(duì)operator=進(jìn)行重載,那么當(dāng)調(diào)用copy時(shí),便會(huì)引發(fā)編譯錯(cuò)誤。這使得類型參數(shù)可以在函數(shù)使用之前便能得到檢驗(yàn),而無需等到對(duì)象被使用時(shí)。
                另一方面,concept參與到特化中后,使得操作分派更加方便:
                concept assignable<T> {
                    T& operator=(T const&);
                }
                concept copyable<T> {
                    T& T::copy(T const&);
                }
                template<assignable T> void copy(T& a, T const& b) {    //#1
                    a=b;
                }
                template<copyable T> void copy(T& a, T const& b) {    //#2
                    a.copy(b);
                }
                X x1,x2; //X支持operator=操作符
                Y y1,y2; //Y擁有copy成員函數(shù)
                copy(x1, x2);    //使用#1
                copy(y1, y2);    //使用#2
                在靜多態(tài)中,concept很好地提供了類型約束。既然同樣是Unbound,那么concept是否同樣可以被用于Runtime Unbound?應(yīng)當(dāng)說可以,但不是現(xiàn)有的concept。在Runtime Unbound多態(tài)中,需要運(yùn)行時(shí)的concept。
                依舊使用繪圖案例做一個(gè)演示。假設(shè)這里使用的"C++"已經(jīng)支持concept,并且也支持了運(yùn)行時(shí)的concept:

                class Rectangle

                {

                    void load(xml init) {...}

                    void draw(monitor dev) {...}

                    ...

                };

                class Ellipse

                {

                    void load(xml init) {...}

                    void draw(monitor dev) {...}

                    ...

                };

                ...

                concept Shape<T> {

                    void T::load(xml init);

                    void T::draw(monitor dev);

                }

                ...

                void DrawShapes(monitor dev, vector<Shape> const& g)

                {

                    vector<IShape>::const_iterator b(g.begin()), e(g.end());

                    for(; b!=e; ++b)

                    {

                        (*b).draw(dev);

                    }

                }

                ...

                vector<Shape> vg;

                vg.push_back(Rectangle(...));

                vg.push_back(Ellipse(...));

                vg.push_back(string("xxx"));    //錯(cuò)誤,不符合Shape concept

                ...

                DrawShapes(crt, vg);

                乍看起來沒什么特別的,但是請(qǐng)注意vector<Shape>。這里使用一個(gè)concept,而不是一個(gè)具體的類型,實(shí)例化一個(gè)模板。這里的意思是說,這個(gè)容器接受的是所有符合Shape concept的對(duì)象,類型不同也沒關(guān)系。當(dāng)push進(jìn)vg的對(duì)象不符合Shape,便會(huì)發(fā)生編譯錯(cuò)誤。

                 但是,最關(guān)鍵的東西不在這里。注意到DrawShapes函數(shù)了嗎?由于vector<Shape>中的元素類型可能完全不同。語(yǔ)句 (*b).draw(dev);的語(yǔ)義在靜態(tài)語(yǔ)言中是非法的,因?yàn)槲覀兏緹o法在編譯時(shí)具體確定(*b)的類型,從而鏈接正確的draw成員。而在這里, 由于我們引入了Runtime Unbound,對(duì)于對(duì)象的訪問鏈接發(fā)生在運(yùn)行時(shí)。因此,我們便可以把不同類型的對(duì)象存放在一個(gè)容器中。

                concept在這里起到了類型檢驗(yàn)的作用,不符合相應(yīng)concept的對(duì)象是無法放入這個(gè)容器的,從而在此后對(duì)對(duì)象的使用的時(shí)候,也不會(huì)發(fā)生類型失配的 問題。這也就在動(dòng)態(tài)的機(jī)制下確保了類型安全。動(dòng)多態(tài)確保類型安全依靠靜態(tài)類型。也就是所有類型都從一個(gè)抽象接口上繼承,從而將類型歸一化,以獲得建立在靜 態(tài)類型系統(tǒng)之上的類型安全。而concept的類型安全保證來源于對(duì)類型特征的描述,是一種非侵入的接口約束,靈活性大大高于類型歸一化的動(dòng)多態(tài)。

                如果我們引入這樣一個(gè)規(guī)則:如果用類型創(chuàng)建實(shí)例(對(duì)象),那么所創(chuàng)建的對(duì)象是靜態(tài)鏈接的,也就是編譯時(shí)鏈接;而用concept創(chuàng)建一個(gè)對(duì)象,那么所創(chuàng)建的對(duì)象是動(dòng)態(tài)鏈接的,也就是運(yùn)行時(shí)鏈接。

                在這條規(guī)則的作用下,下面這段簡(jiǎn)單的代碼將會(huì)產(chǎn)生非常奇妙的效果:

                class nShape

                {

                public:

                    nShape(Shape g, int n) : m_graph(g), m_n(n) {}

                    void setShape(Shape g) {

                        m_graph=g;

                    }

                private:

                    Shape    m_graph;

                    int        m_n;

                };

                在規(guī)則的作用下,m_graph是一個(gè)動(dòng)態(tài)對(duì)象,它的類型只有在運(yùn)行時(shí)才能明確。但是無論什么類型,必須滿足Shape concept。而m_n的類型是確定的,所以是一個(gè)靜態(tài)對(duì)象

                這和傳統(tǒng)的模板有區(qū)別嗎?模板也可以用不同的類型參數(shù)定義成員數(shù)據(jù)。請(qǐng)看如下代碼:

                Rectangle r;

                Ellipse e;
                 nShape(r, 10);

                 nShape.setShape(e);   //對(duì)于傳統(tǒng)模板而言,這個(gè)操作是非法的,因?yàn)閑和r不是同一種類型

                動(dòng)態(tài)對(duì)象的特點(diǎn)在于,我們可以在對(duì)象創(chuàng)建后,用一個(gè)不同類型的動(dòng)態(tài)對(duì)象代替原來的,只需要這些對(duì)象符合相應(yīng)的concept。這在靜態(tài)的模板上是做不到的。

                現(xiàn)在回過頭來看一下用concept實(shí)例化模板的情形。我們知道,用一個(gè)類型實(shí)例化一個(gè)模板,得到的是一個(gè)類,或者說類型。而用一個(gè)concept實(shí)例化 一個(gè)模板,得到的又是什么呢?還是一個(gè)concept。那么vector<Shape>是一個(gè)concept,因而它的實(shí)例是動(dòng)態(tài)的對(duì)象。當(dāng) 然,實(shí)際上沒有必要把vector<Shape>的實(shí)例整個(gè)地當(dāng)成動(dòng)態(tài)對(duì)象,它只是具有動(dòng)態(tài)對(duì)象的行為特征。在實(shí)現(xiàn)上,vector< Shape>可以按照普通模板展開,而其內(nèi)部由concept模板實(shí)參定義的對(duì)象作為動(dòng)態(tài)對(duì)象處理即可。一個(gè)由concept實(shí)例化的模板的對(duì)象作為語(yǔ)義上的動(dòng)態(tài)對(duì)象
                下面的代碼則引出了另一個(gè)重要的特性:
                vector<float> vFloat;    //靜態(tài)對(duì)象的容器,內(nèi)部存放的都是靜態(tài)對(duì)象,屬于同一類型float
                vector<Shape> vShape; //動(dòng)態(tài)對(duì)象的容器,內(nèi)部存放動(dòng)態(tài)對(duì)象,都符合Shape
                同一個(gè)類模板,當(dāng)使用類型實(shí)例化,執(zhí)行static unbound多態(tài)使用concept實(shí)例化,執(zhí)行runtime unbound多態(tài)。兩者的形式相同。也就是說static多態(tài)同runtime多態(tài)以相同的形式表達(dá)。 由于concept的加入,兩種完全不同的多態(tài)被統(tǒng)一在同一個(gè)模型和形式下。實(shí)際上,static和runtime unbound多態(tài)可以看作同一個(gè)抽象體系的兩個(gè)分支,分別處理不同情況的應(yīng)用。而形式上的統(tǒng)一,則更加接近抽象體系的本質(zhì)。同時(shí),也使得兩種 unbound多態(tài)的差異被后臺(tái)化,使用者無需額外的工作,便可以同時(shí)獲得動(dòng)態(tài)和靜態(tài)的抽象能力。同時(shí),兩種多態(tài)所展示的邏輯上的對(duì)稱性,也暗示了兩者在 本質(zhì)上的聯(lián)系。這里統(tǒng)一的形式,便是這種對(duì)稱性的結(jié)果。
                對(duì)于模板函數(shù),則會(huì)表現(xiàn)出更加有趣的特性(這個(gè)函數(shù)模板有些特別,不需要template關(guān)鍵字和類型參數(shù)列表,這是我偽造的。但由于concept的使用,它本質(zhì)上還是一個(gè)模板):
                void draw(Shape g);
                這個(gè)函數(shù)接受一個(gè)符合Shape的參數(shù)。如果我們用一個(gè)靜態(tài)對(duì)象調(diào)用這個(gè)函數(shù):
                Rectangle r;
                draw(r);
                那么,就執(zhí)行static unbound,實(shí)例化成一個(gè)完完整整的函數(shù),同傳統(tǒng)的函數(shù)模板一樣。
                如果用一個(gè)動(dòng)態(tài)對(duì)象調(diào)用這個(gè)函數(shù):
                Shape g=Cycle();
                draw(g);
                g=Rectangle();
                draw(g);
                那么,就執(zhí)行runtime unbound,生成一個(gè)等待運(yùn)行時(shí)鏈接的函數(shù)。上面的兩次調(diào)用,分別進(jìn)行了兩次運(yùn)行時(shí)鏈接,以匹配不同的動(dòng)態(tài)對(duì)象。
                這樣,我們可以通過函數(shù)調(diào)用時(shí)的參數(shù)對(duì)象,來控制使用不同的多態(tài)形式。更復(fù)雜的情況就是用一個(gè)函數(shù)的返回值調(diào)用另一個(gè)函數(shù),這樣構(gòu)成的調(diào)用鏈依然符合上述的調(diào)用控制原則。
                下面,我們將看到Runtime Unbound多態(tài)的一個(gè)精彩表演:
                //假設(shè),我們已經(jīng)定義了Rectangle、Cycle、Square、Ellipse、Trangle五個(gè)類,
                // 分別map到Rectangles、Cycles、Squares、Ellipses、Trangles五個(gè)concept上,
                // 這些concept都refine(可以不正確地理解為繼承吧)自Shape。
                void draw(monitor dev, Rectangles r); //#3
                void draw(monitor dev, Cycles c);       //#4
                void draw(monitor dev, Squares s);    //#5
                void draw(monitor dev, Ellipses e);    //#6
                void draw(monitor dev, Trangles t);    //#7
                //此處定義一個(gè)Shape的動(dòng)態(tài)對(duì)象
                Shape g=CreateShapeByUserInput();    //這個(gè)函數(shù)根據(jù)用戶輸入創(chuàng)建圖形對(duì)象,所以圖形對(duì)象的類型只能到運(yùn)行時(shí)從能確定。
                draw(crt, g);
                好了,現(xiàn)在該調(diào)用哪個(gè)版本的draw?根據(jù)用戶的輸入來。換句話說,調(diào)用哪個(gè)版本的draw,取決于CreateShapeByUserInput()函數(shù)的返回結(jié)果,也就是用戶輸入的結(jié)果。如果CreateShapeByUserInput() 返回Rectangle的動(dòng)態(tài)對(duì)象,那么執(zhí)行#3;如果返回的是Trangle對(duì)象,那么執(zhí)行#7。這是一種動(dòng)態(tài)分派的操作。在運(yùn)行時(shí)concept的作 用下,實(shí)現(xiàn)起來非常容易。對(duì)draw的調(diào)用最終會(huì)被轉(zhuǎn)換成一個(gè)concept需求表,來自draw函數(shù),每一項(xiàng)對(duì)應(yīng)一個(gè)函數(shù)版本,并且指明了所對(duì)應(yīng)的 concept。動(dòng)態(tài)對(duì)象上也有一個(gè)concept表,每一項(xiàng)存放了這個(gè)對(duì)象所符合的concept。用這兩個(gè)表相互匹配,可以找到g對(duì)象的 concept最匹配的那個(gè)draw版本,然后調(diào)用。
                這實(shí)際上是將重載決斷放到運(yùn)行時(shí)進(jìn)行,而concept在其中起到了匹配參數(shù)的作用。
                這樣的做法同利用rtti信息執(zhí)行類型分派調(diào)用類似:
                void draw_impl(monitor dev, Rectangle& r);
                void draw_impl(monitor dev, Cycle& c);
                void draw_impl(monitor dev, Square& s);
                void draw_impl(monitor dev, Ellipse& e);
                void draw_impl(monitor dev, Trangle& t);
                void draw_impl(monitor dev, Shape& g) {
                    if(typeif(g)==typeid(Rectangle))
                        draw_impl(dev, (Rectangle&)g);
                    else if(typeif(g)==typeid(Cycle))
                        draw_impl(dev, (Cycle&)g);
                    ...
                }
                但是,他們卻有著天壤之別。首先,rtti分派是侵入的。如果需要增加一個(gè)圖形,需要在draw函數(shù)中增加分派代碼。而Runtime Unbound方案則只需要用新的concept重載draw函數(shù)即可。
                其次,rtti版本有多少圖形類,就需要多少if...else...,而Runtime Unbound則是一對(duì)多的。如果有幾個(gè)圖形類內(nèi)容不同,但有相同的接口,符合同一個(gè)concept,那么只需針對(duì)concept編寫一個(gè)函數(shù)版本即可。 比如,如果有一個(gè)特別的CycleEx類,使用外界正方形的左上角/右下角坐標(biāo)描述,正好符合Ellipses concept,那么只需將CycleEx map到Ellipses上即可,無需多加任何代碼。
                最后,rtti需要獲取類型信息,然后做線性比較,性能無法優(yōu)化。但Runtime Unbound通過concept表的相互匹配,僅牽涉數(shù)值操作,有很大的優(yōu)化空間。
                那么這樣一種運(yùn)行時(shí)分派有什么好處呢?我們看到圖形類上的draw函數(shù)接受一個(gè)monitor類型參數(shù),它代表設(shè)備。如果哪一天需要向另一種設(shè)備,比如 printer,輸出圖形,那么就需要在圖形類上增加另一個(gè)版本的draw函數(shù)。如果類是別人開發(fā)的,那么就增加溝通的負(fù)擔(dān)。如果類是外來的,我們無法修 改,那么只能通過adapter之類的笨拙手段處理。為了讓monitor之類同圖形本身沒有關(guān)聯(lián)的東西分離,應(yīng)當(dāng)使用自由函數(shù)執(zhí)行draw操作。但普通 函數(shù)只能接受確定的類型重載,而傳統(tǒng)的函數(shù)模板則限于編譯期使用,無法進(jìn)行運(yùn)行時(shí)分派。所以,如果能夠使用concept重載函數(shù),并且賦予 Runtime Unbound機(jī)能,那么便可以用最簡(jiǎn)單的形式針對(duì)一類類型進(jìn)行處理,效能高得多。

            運(yùn)行時(shí)concept

               語(yǔ)言層面的concept無法做到這些,因?yàn)樗蔷幾g期機(jī)制。為此,我們需要有一種運(yùn)行時(shí)的concept,或者說二進(jìn)制級(jí)別的concept。

                一個(gè)concept包含了與一個(gè)或若干個(gè)類型有關(guān)的一組函數(shù),包括成員函數(shù)和自由函數(shù)。于是,我們就可以用一個(gè)類似“虛表”的函數(shù)指針表(暫且稱為 ctable吧)存放concept指定的函數(shù)指針。這樣的ctable依附在動(dòng)態(tài)對(duì)象上,就像vtable一樣。每個(gè)對(duì)象都會(huì)匹配和map到若干個(gè) concept。因此,每個(gè)動(dòng)態(tài)對(duì)象會(huì)有一個(gè)concept表,其中存放著指向各ctable的指針,以及相應(yīng)的concept基本信息。

                當(dāng)一個(gè)“用戶”(函數(shù)或模板)需要在運(yùn)行時(shí)鏈接到對(duì)象上的時(shí)候,它會(huì)提交一個(gè)concept的代碼(全局唯一)。系統(tǒng)用這個(gè)代碼在動(dòng)態(tài)對(duì)象的 concept表上檢索,獲得指向所需concept的指針,并且填寫到“用戶”給出的一個(gè)“插入點(diǎn)”(一個(gè)指針)中。隨后“用戶”便可以直接通過這個(gè) “插入點(diǎn)”間接調(diào)用所需的函數(shù),成員或自由函數(shù)。

                在這里,concept的巧妙之處在于,將一族函數(shù)集合在一起,作為一個(gè)整體(即接口)。那么,在執(zhí)行運(yùn)行時(shí)匹配的時(shí)候,不再是一個(gè)函數(shù)一個(gè)函數(shù)地查詢, 可以一次性地獲知這些函數(shù)是否存在。這就很容易地規(guī)避了類型安全保證操作的損耗。如果使用hash查詢,那么可以在O(1)實(shí)現(xiàn)concept匹配。另 外,一個(gè)concept的hash值可以在編譯時(shí)計(jì)算好,運(yùn)行時(shí)鏈接只需執(zhí)行hash表檢索,連hash值計(jì)算也可以省去。

                一個(gè)動(dòng)態(tài)對(duì)象可以直接用指向concept ctable的指針表示。在不同concept之間轉(zhuǎn)換,相當(dāng)于改變指針的指向,這種操作非常類似OOP中的dynamic_cast。

                對(duì)于如下的動(dòng)態(tài)對(duì)象定義:

                Shape g=Cycle();

                會(huì)創(chuàng)建一個(gè)Cycle對(duì)象,在對(duì)象上構(gòu)建起一個(gè)concept表,表中對(duì)應(yīng)Cycle所有符合的concept。并且建立一組ctable,每個(gè) ctable對(duì)應(yīng)一個(gè)concept。每個(gè)concept表項(xiàng)指向相應(yīng)的ctable。而符號(hào)g則實(shí)際上是指向所建立對(duì)象的Shapes ctable的指針。

                對(duì)于函數(shù):

                void draw(Shape g);

                draw(g);

                調(diào)用g時(shí),由于draw的參數(shù)是Shape concept,而g正是draw所需的concept,所以無需在對(duì)象g的concept表上匹配,可以直接使用這個(gè)ctable指針。這就是說,只要 所用動(dòng)態(tài)對(duì)象(g)的concept同使用方(draw函數(shù))能夠匹配,便可以直接使用指向ctable的指針鏈接(編譯時(shí)鏈接),無需在運(yùn)行時(shí)重新匹 配。只有發(fā)生concept轉(zhuǎn)換時(shí),才需要在concept表中搜索,獲得所需的ctable指針:

                Swappable s=g; //Swappable是另一個(gè)concept

                這種情況同dynamic_cast極其相似。也可以模仿著采用concept_cast之類的操作符,使得concept轉(zhuǎn)換顯式化,消除隱式轉(zhuǎn)換的問題(強(qiáng)concept化)。

                所以,Runtime Unbound在運(yùn)行時(shí)concept的作用下,具有同動(dòng)多態(tài)相同的底層行為。因而,他們的性能也是一樣的。很多用于動(dòng)多態(tài)的方案和算法都可以直接用于運(yùn)行時(shí)concept。

            Runtime Unbound和Runtime Bound

                對(duì)于runtime unbound同runtime bound之間的差異前面已經(jīng)有所展示。在其他方面,兩者還存在更多的差別。
                首先,就像繪圖案例中展示的那樣,runtime unbound是非侵入的。runtime unbound不要求類型繼承自同一類型,只需將類型同concept關(guān)聯(lián)起來便可。
                其次,concept不是一種局限于OO的技術(shù),不僅涉及成員函數(shù),還包括了自由函數(shù),范圍更廣,更加靈活。
                最后,實(shí)現(xiàn)上,Runtime Unbound和Runtime Bound之間有驚人的相似之處。兩者都采用一個(gè)函數(shù)指針表作為操作分派;都采用一個(gè)指向函數(shù)表的指針作為入口;一個(gè)動(dòng)態(tài)對(duì)象上的concept之間的轉(zhuǎn) 換,也同動(dòng)多態(tài)對(duì)象一樣,在不同的函數(shù)表間切換。他們唯一的不同,是實(shí)現(xiàn)接口的機(jī)制。
                動(dòng)多態(tài)用類型兼任接口,通過繼承和虛函數(shù)實(shí)現(xiàn)接口的功能。用類型作為類型的接口,使得這兩個(gè)本來獨(dú)立的概念交織在一起。增加整個(gè)類型體系的復(fù)雜度和耦合度。    concept則利用獨(dú)立的系統(tǒng)描述、表達(dá)和管理接口。類型則回歸到表達(dá)業(yè)務(wù)對(duì)象的功能上來。
                動(dòng)多態(tài)在使用類型表達(dá)接口的時(shí)候,便很容易地引入一個(gè)麻煩的問題,表達(dá)功能的類型和表達(dá)接口的類型混合在一起,使用時(shí)必須通過一些方法區(qū)分出哪些是接口, 哪些是功能類型。這增加了對(duì)象模型的復(fù)雜性。而concept則獨(dú)立于類型體系之外,所有對(duì)接口的操作都是單一的,檢索和匹配來得更加方便快捷。
                作為繼承體系的基礎(chǔ)部分,動(dòng)多態(tài)的抽象接口必須在繼承結(jié)構(gòu)的最頂端。那么這些抽象類型必須先于其他類型出現(xiàn)。這對(duì)系統(tǒng)的早期設(shè)計(jì)產(chǎn)生很大的壓力,往往一個(gè)基礎(chǔ)抽象接口設(shè)計(jì)有誤,便會(huì)造成整個(gè)體系的變更。
                而concept是獨(dú)立于類型的,那么任何時(shí)候都可以將一個(gè)類型同接口綁定。接口甚至可以在類型體系基本建立之后才確定。這種靈活性對(duì)復(fù)雜軟件的開發(fā)至關(guān)重要,去掉了長(zhǎng)期以來套在人們頭上的枷鎖。
                前面已經(jīng)提到,在不需要concept轉(zhuǎn)換的情況下,無需執(zhí)行運(yùn)行時(shí)的concept匹配,所有的調(diào)用具有同動(dòng)多態(tài)一樣的效率(都是間接調(diào)用)。在執(zhí)行 concept轉(zhuǎn)換時(shí),無需象動(dòng)多態(tài)那樣在復(fù)雜的繼承體系上檢索,只需執(zhí)行concept表的hash匹配,效率反而更高,而且更簡(jiǎn)單。考慮到這些情況, 我們可以認(rèn)為concept化的Runtime Unbound多態(tài)完全能夠替代傳統(tǒng)的動(dòng)多態(tài)。也就是說,我們不再需要?jiǎng)佣鄳B(tài)了
                 想象一下,如果一門語(yǔ)言能夠擁有運(yùn)行時(shí)concept,那么它完全可以只保留Static Unbound和Runtime Unbound多態(tài),而放棄Runtime Bound多態(tài)。一旦放棄動(dòng)多態(tài)(沒有了虛函數(shù)和虛表),那么對(duì)象模型便可以大大簡(jiǎn)化。所有對(duì)象只需要線性分布,基類和成員依次堆疊在一起,也沒有 vtable的干擾,對(duì)象結(jié)構(gòu)可以做到最簡(jiǎn)單。同時(shí),繼承也回歸了代碼重用的傳統(tǒng)用途。而且,對(duì)象獨(dú)立于接口存儲(chǔ),在能夠在編譯時(shí)靜態(tài)鏈接的時(shí)候,可以作 為靜態(tài)對(duì)象使用。而在需要?jiǎng)討B(tài)對(duì)象的地方,又可以很容易地轉(zhuǎn)換成動(dòng)態(tài)對(duì)象,只需要為其附上concept表和ctable。一切都簡(jiǎn)化了。對(duì)象模型也更加 容易統(tǒng)一。
                這對(duì)于很多底層開發(fā)的程序員對(duì)于c++復(fù)雜而又混亂的對(duì)象模型難以接受。如果能夠廢除虛函數(shù),簡(jiǎn)化對(duì)象模型,那么對(duì)于這些底層開發(fā)而言,將會(huì)帶來直接的好 處。只要確保不使用concpt定義對(duì)象、實(shí)例化模板,便可以使整個(gè)軟件執(zhí)行Static Unbound。這相當(dāng)于去掉OOP的C++。否則,就啟用Runtime Unbound,實(shí)現(xiàn)運(yùn)行時(shí)多態(tài)。

            總結(jié)

                Static Unbound和Runtime Unbound作為一對(duì)親密無間的多態(tài)技術(shù),體現(xiàn)了最完善的抽象形式。兩者各踞一方,相互補(bǔ)充,相互支援。而且兩者具有統(tǒng)一的表現(xiàn)形式,大大方便了使用, 對(duì)于軟件工程具有非凡的意義。另一方面,Runtime Bound多態(tài)作為OO時(shí)代的產(chǎn)物,體現(xiàn)了靜態(tài)類型語(yǔ)言在運(yùn)行時(shí)多態(tài)方面的最大努力。但是,隨著運(yùn)行時(shí)concept的引入,Runtime Unbound多態(tài)自身存在的靜態(tài)類型安全問題和性能問題,都能夠得到很好的解決。至此,Runtime Unbound便具備了替代Runtime Bound的實(shí)力。相信在不久的將來,Runtime Bound將會(huì)逐漸步入它的黃昏。

            參考

            1. http://groups.google.com/group/pongba/web/Runtime+Polymorphic+Generic +Programming.pdf。大牛人Jaakko Järvi等寫的關(guān)于Runtime concept的文章,講解了runtime concept的概念的實(shí)現(xiàn)方法,并在ConceptC++上以庫(kù)的形式實(shí)現(xiàn)。其中使用傳統(tǒng)的動(dòng)多態(tài)實(shí)現(xiàn)runtime concept,這表明動(dòng)多態(tài)的實(shí)現(xiàn)機(jī)制同runtime concept是一致的。當(dāng)然庫(kù)的實(shí)現(xiàn)很復(fù)雜,這是“螺螄殼里做道場(chǎng)”,無奈之舉。Runtime concept還是應(yīng)當(dāng)在語(yǔ)言中first-class地實(shí)現(xiàn)。
            2. http://www.lubomir.org/academic/MinimizingCodeBloat.pdf。也是Jaakko Järvi寫的,運(yùn)行時(shí)分派的文章。
            3. http://opensource.adobe.com/wiki/index.php/Runtime_Concepts。
            4. Inside C++ Object Model。

            附錄 Runtime Concept的具體實(shí)現(xiàn)

                我們有一個(gè)concept:
                concept Shape<T>
                {
                    void T::load(xml);
                    void T::draw(device);
                    void move(T&);
                }
                另外,還有一個(gè)代表圓的concept:
                concept Cycles<T> :
                    CopyConstructable<T>,
                    Assignable<T>,
                    Swappable<T>,
                    Shape<T>
                {
                    T::T(double, double, double);
                    double T::getX();
                    double T::getY();
                    double T::getR();
                    void T::setX(double);
                    void T::setY(double);
                    void T::setR(double);
                }
                現(xiàn)在有類型Cycle:
                class Cycle
                {
                public:
                    Cycle(double x, double y, double r);
                    Cycle(Cycle const& c);
                    Cycle& operator=(Cycle const& c);
                    void swap(Cycle const& c);
                    void load(xml init);
                    void draw(device dev);
                    double getX();
                    double getY();
                    double getR();
                    void setX(double x);
                    void setY(double y);
                    void setR(double r);
                private:
                    ...
                };
                當(dāng)定義一個(gè)動(dòng)態(tài)對(duì)象:
                Shape g=Cycle();
                便會(huì)形成如下圖的結(jié)構(gòu):

                g實(shí)際上是一個(gè)指針,指向concept表的Shape項(xiàng),而Shape項(xiàng)指向Shape對(duì)應(yīng)的ctable。由于Cycle refine自Shape等眾多concept,那么Cycle的ctable實(shí)際上包含了這些concept的ctable,所以只需一個(gè)Cycle的 ctable,而其他concept都分別指向其中各自相應(yīng)的部分。ctable中的每一個(gè)項(xiàng)則指向具體的函數(shù)體。
                如果遇到語(yǔ)句:
                Swappable h=concept_cast<Swappable>(g);
                那么,將會(huì)執(zhí)行一個(gè)搜索,用concept Swappable的id(比如hash碼)在concept表中檢索是否存在Swappable項(xiàng)。如果存在,就將對(duì)應(yīng)項(xiàng)的指針賦給h。這種操作同 dynamic_cast操作非常相似,只是相比在復(fù)雜的對(duì)象結(jié)構(gòu)中查詢更加簡(jiǎn)單迅速。
                concept表置于對(duì)象的頭部或尾部,這是為了便于對(duì)象檢索concept接口。每個(gè)類型的ctable只需一份。
                對(duì)象本體可以很容易地同concept表分離,在完全靜態(tài)的情況下,concept表是不需要的。如果需要runtime多態(tài),加上concept表即可。

            posted @ 2007-12-06 17:20 longshanks 閱讀(4232) | 評(píng)論 (12)編輯 收藏

            久久久久噜噜噜亚洲熟女综合| 久久久久亚洲AV无码网站| 伊人久久综合热线大杳蕉下载| 欧美黑人激情性久久| 久久综合亚洲色HEZYO社区| 久久夜色撩人精品国产小说| 99热都是精品久久久久久| www.久久热| 久久国产精品久久久| 精品免费久久久久久久| 久久ww精品w免费人成| 精品久久久久久亚洲精品| 久久精品国产久精国产思思 | 国产精品日韩欧美久久综合| 久久99国产精品99久久| 国产精品久久久久…| 精品国产一区二区三区久久| 精品久久久久久中文字幕| AV狠狠色丁香婷婷综合久久 | 久久亚洲欧美国产精品| 日韩精品久久无码人妻中文字幕| 亚洲中文字幕无码久久2020| 精品一二三区久久aaa片| 亚洲午夜久久久久妓女影院| 日韩精品无码久久久久久| 狠狠色丁香婷婷综合久久来 | 久久国产欧美日韩精品| 97久久香蕉国产线看观看| 夜夜亚洲天天久久| 久久久99精品成人片中文字幕| 人妻丰满?V无码久久不卡| 偷偷做久久久久网站| 久久中文骚妇内射| 亚洲精品国产成人99久久| 亚洲精品tv久久久久久久久久| 中文字幕日本人妻久久久免费| 久久人人爽人人爽人人AV东京热| 国产精品美女久久久m| 精品久久久久久国产免费了| 久久久久亚洲精品日久生情| 久久精品中文騷妇女内射|