正文

回到頂部

1. TMP是什么?

模板元編程(template metaprogramming TMP)是實(shí)現(xiàn)基于模板的C++程序的過(guò)程,它能夠在編譯期執(zhí)行。你可以想一想:一個(gè)模板元程序是用C++實(shí)現(xiàn)的并且可以在C++編譯器內(nèi)部運(yùn)行的一個(gè)程序,它的輸出——從模板中實(shí)例化出來(lái)的C++源碼片段——會(huì)像往常一樣被編譯。

回到頂部

2. 使用TMP的優(yōu)勢(shì)

如果這沒(méi)有沖擊到你,是因?yàn)槟銢](méi)有足夠盡力去想。

 

C++不是為了模板元編程而設(shè)計(jì)的,但是自從TMP早在1990年被發(fā)現(xiàn)之后,它就被證明是非常有用的,為了使TMP的使用更加容易,在C++語(yǔ)言和標(biāo)準(zhǔn)庫(kù)中加入了一些擴(kuò)展。是的,TMP是被發(fā)現(xiàn)的,而不是被發(fā)明。當(dāng)模板被添加到C++中的時(shí)候TMP這個(gè)特性就被引入了。對(duì)于某些人來(lái)說(shuō)所有需要做的就是關(guān)注如何以一種聰明的和意想不到的方式來(lái)使用它。

TMP有兩種強(qiáng)大的力量。第一,它使得一些事情變得容易也即是說(shuō)如果沒(méi)有TMP,這些事情做起來(lái)很難或者不可能實(shí)現(xiàn)。第二,因?yàn)槟0逶幊淘?/strong>C++編譯期執(zhí)行,它們可以將一些工作從運(yùn)行時(shí)移動(dòng)到編譯期。一個(gè)結(jié)果就是一些原來(lái)通常在運(yùn)行時(shí)能夠被發(fā)現(xiàn)的錯(cuò)誤,現(xiàn)在在編譯期就能夠被發(fā)現(xiàn)了。另外一個(gè)結(jié)果就是使用TMP的C++程序在基本上每個(gè)方面都更加高效:更小的執(zhí)行體,更短的運(yùn)行時(shí)間,更少的內(nèi)存需求。(然而,將工作從運(yùn)行時(shí)移到編譯期的一個(gè)后果就是編譯時(shí)間增加了。使用TMP的程序比沒(méi)有使用TMP的程序可能消耗更長(zhǎng)的時(shí)間來(lái)進(jìn)行編譯。)

回到頂部

3. 如何使用TMP?

3.1 再次分析Item 47中的實(shí)例

考慮在Item 47中為STL的advance寫出來(lái)的偽代碼。我已經(jīng)為偽代碼部分做了粗體:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

 1 template<typename IterT, typename DistT> 2 void advance(IterT& iter, DistT d) 3 { 4 if (iter is a random access iterator) { 5  6 iter += d;                           // use iterator arithmetic 7  8 }                                        // for random access iters 9 10 else {                                
11 12 13 if (d >= 0) { while (d--) ++iter; } // use iterative calls to14 else { while (d++) --iter; } // ++ or -- for other15 } // iterator categories16 }

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

 

我們可以使用typeid替換偽代碼,讓程序能夠執(zhí)行。這就產(chǎn)生了一個(gè)“普通的”C++方法——也就是所有工作都在運(yùn)行時(shí)開展的方法:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

 1 template<typename IterT, typename DistT> 2 void advance(IterT& iter, DistT d) 3 { 4 if ( typeid(typename std::iterator_traits<IterT>::iterator_category) == 5 typeid(std::random_access_iterator_tag)) { 6  7 iter += d;                           // use iterator arithmetic 8  9 }                                        // for random access iters10 11 else {                               
12 13 14 if (d >= 0) { while (d--) ++iter; } // use iterative calls to15 else { while (d++) --iter; } // ++ or -- for other16 } // iterator categories17 }

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

 

Item 47指出這種基于typeid的方法比使用trait效率更低,因?yàn)橥ㄟ^(guò)使用這種方法,(1)類型測(cè)試發(fā)生在運(yùn)行時(shí)而不是編譯期(2)執(zhí)行運(yùn)行時(shí)類型測(cè)試的代碼在運(yùn)行的時(shí)候必須可見。事實(shí)上,這個(gè)例子也展示出了為什么TMP比一個(gè)“普通的”C++程序更加高效,因?yàn)閠raits方式屬于TMP。記住,trait使得在類型上進(jìn)行編譯期if…else運(yùn)算成為可能。

我已經(jīng)在前面提到過(guò)一些東西說(shuō)明其在TMP中比在“普通”C++中更加容易,Item 47中也提供了一個(gè)advance的例子。Item 47中提到了advance的基于typeid的實(shí)現(xiàn)會(huì)導(dǎo)致編譯問(wèn)題,看下面的例子:

1 std::list<int>::iterator iter;2 ...3 advance(iter, 10);               // move iter 10 elements forward;4 // won’t compile with above impl.

 

考慮為上面調(diào)用所產(chǎn)生的advance的版本,將模板參數(shù)IterT和DistT替換為iter和10的類型之后,我們得到下面的代碼:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

 1 void advance(std::list<int>::iterator& iter, int d) 2 { 3 if (typeid(std::iterator_traits<std::list<int>::iterator>::iterator_category) == 4 typeid(std::random_access_iterator_tag)) { 5  6 iter += d; 7  8 // error! won’t compile 9 10 11 }12 else {13 if (d >= 0) { while (d--) ++iter; }14 else { while (d++) --iter; }15 }16 }

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

 

有問(wèn)題的是高亮部分,就是使用+=的語(yǔ)句。在這個(gè)例子中,我們?cè)趌ist<int>::iterator上使用+=,但是list<int>::iterator是一個(gè)雙向迭代器(見Item 47),所以它不支持+=。只有隨機(jī)訪問(wèn)迭代器支持+=?,F(xiàn)在,我們知道了+=這一行將永遠(yuǎn)不會(huì)被執(zhí)行到,因?yàn)闉閘ist<int>::iteraotr執(zhí)行的typeid測(cè)試永遠(yuǎn)都不會(huì)為真,但是編譯器有責(zé)任確保所有的源碼都是有效的,即使不被執(zhí)行到,當(dāng)iter不是隨機(jī)訪問(wèn)迭代器“iter+=d”就是無(wú)效代碼。將它同基于tratis的TMP解決方案進(jìn)行比較,后者把為不同類型實(shí)現(xiàn)的代碼分別放到了不同的函數(shù)中,每個(gè)函數(shù)中進(jìn)行的操作只針對(duì)特定的類型。

3.2 TMP是圖靈完全的

TMP已經(jīng)被證明是圖靈完全的(Turing-Complete),這也就意味著它足夠強(qiáng)大到可以計(jì)算任何東西。使用TMP,你可以聲明變量,執(zhí)行循環(huán),實(shí)現(xiàn)和調(diào)用函數(shù)等等。但是這些概念同“普通”C++相對(duì)應(yīng)的部分看起來(lái)非常不同。例如,Item 47中if…else條件在TMP中是如何通過(guò)使用模板和模板特化來(lái)表現(xiàn)的。但這是程序級(jí)別(assembly-level)的TMP。TMP庫(kù)(例如,Boost MPL,見Item 55)提供了更高級(jí)別的語(yǔ)法,這些語(yǔ)法不會(huì)讓你誤認(rèn)為是“普通的”C++。

3.3 TMP中的循環(huán)通過(guò)遞歸來(lái)實(shí)現(xiàn)

再瞥一眼事情在TMP中是如何工作的,讓我們看一下循環(huán)。TMP中沒(méi)有真正的循環(huán)的概念,所以循環(huán)的效果是通過(guò)遞歸來(lái)完成的。(如果一提到遞歸你就不舒服,在進(jìn)入TMP 冒險(xiǎn)之前你就需要處理好它。TMP主要是一個(gè)函數(shù)式語(yǔ)言,遞歸對(duì)于函數(shù)式語(yǔ)言就如同電視對(duì)美國(guó)流行文化一樣重要:它們是不可分割的。)即使是遞歸也不是普通的遞歸,因?yàn)門MP循環(huán)沒(méi)有涉及到遞歸函數(shù)調(diào)用,所涉及到的是遞歸模板實(shí)例化(template instantiations)。

TMP的“hello world”程序是在編譯期計(jì)算階乘。它算不上是令人激動(dòng)的程序,“hello world”也不是,但是這兩個(gè)例子對(duì)于介紹語(yǔ)言都是有幫助的。TMP階乘計(jì)算通過(guò)對(duì)模板實(shí)例進(jìn)行遞歸來(lái)對(duì)循環(huán)進(jìn)行示范。也同樣示范了變量是如何在TMP中被創(chuàng)建和使用的,看下面的代碼:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

 1 template<unsigned n>          // general case: the value of 2  3 struct Factorial {                   // Factorial<n> is n times the value 4  5  6 // of Factorial<n-1> 7 enum { value = n * Factorial<n-1>::value }; 8 }; 9 template<> // special case: the value of10 struct Factorial<0> { // Factorial<0> is 111 enum { value = 1 };12 };

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

 

考慮上面的模板元編程(真的僅僅是單一的元函數(shù)Factorial),你通過(guò)引用Factorial<n>::value來(lái)得到factorial(n)的值。

代碼的循環(huán)部分發(fā)生在模板實(shí)例Factorial<n>引用模板實(shí)例Factorial<n-1>的時(shí)候。像所有遞歸一樣,有一種特殊情況來(lái)讓遞歸終止。在這里是模板特化Factorial<0>。

每個(gè)Factorial模板的實(shí)例都是一個(gè)結(jié)構(gòu)體,每個(gè)結(jié)構(gòu)體使用enum hack(Item 2)來(lái)聲明一個(gè)叫做value的TMP變量。Value持有遞歸計(jì)算的當(dāng)前值。如果TMP有一個(gè)真正的循環(huán)結(jié)構(gòu),value將會(huì)每次循環(huán)的時(shí)候進(jìn)行更新。既然TMP使用遞歸模板實(shí)例來(lái)替換循環(huán),每個(gè)實(shí)例會(huì)得到它自己的value的拷貝,每個(gè)拷貝都會(huì)有一個(gè)和“循環(huán)”中位置想對(duì)應(yīng)的合適的值。

你可以像下面這樣使用Facorial:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

1 int main()2 {3 std::cout << Factorial<5>::value; // prints 1204 5 std::cout << Factorial<10>::value;       // prints 36288006 7 }

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

                                             

 如果你認(rèn)為這比冰激凌更酷,你就已經(jīng)獲得模板元程序員需要的素材。如果模板和特化,遞歸實(shí)例和enum hacks,還有像Factorial<n-1>::value這樣的輸入使你毛骨悚然,你還是一個(gè)“普通的”C++程序員。

3.4 TMP還能夠做什么?

當(dāng)然,F(xiàn)actorial對(duì)TMP的功能進(jìn)行了示范,如同“hello world”程序?qū)θ魏蝹鹘y(tǒng)編程語(yǔ)言的功能進(jìn)行示范一樣。為了讓你明白為什么TMP是值得了解的,知道它能夠做什么很重要,這里有三個(gè)例子:

  • 確保因次單位(dimensional unit)的正確性。在科學(xué)和工程應(yīng)用中,把因次單位(例如,質(zhì)量,距離和時(shí)間)正確的拼到一起是很必要的。將表示質(zhì)量的變量賦值給表示速度的變量是錯(cuò)誤的,但是用距離變量除以時(shí)間變量然后將結(jié)果賦值被速度變量就沒(méi)有問(wèn)題。通過(guò)使用TMP,確保(在編譯期間)程序中的所有因次單元組合的正確性就是可能的,不管計(jì)算有多復(fù)雜。(這也是使用TMP來(lái)偵測(cè)早期錯(cuò)誤的一個(gè)例子。)TMP這種用法的一個(gè)有趣的方面是它能夠支持分?jǐn)?shù)因次的指數(shù)。這需要在編譯期間將分?jǐn)?shù)簡(jiǎn)化,然后編譯器才能夠確認(rèn),例如,單元 time1/2同time4/8是相同的。

  • 優(yōu)化矩陣操作。Item 21中解釋了有一些函數(shù)(包括 operator*)必須返回新的對(duì)象,Item 44中引入了SquareMatrix類,考慮下面的代碼:

    1 typedef SquareMatrix<double, 10000> BigMatrix;
    2 BigMatrix m1, m2, m3, m4, m5; // create matrices and
    3 ... // give them values
    4 BigMatrix result = m1 * m2 * m3 * m4 * m5; // compute their product

     用“普通的”方式來(lái)計(jì)算result會(huì)有四次創(chuàng)建臨時(shí)matrice對(duì)象的調(diào)用,每次調(diào)用都應(yīng)用在對(duì)operator*調(diào)用的返回值上面。這些獨(dú)立的乘法在矩陣元素上產(chǎn)生了四          次循環(huán)。使用TMP的高級(jí)模板技術(shù)——表達(dá)式模板(expression templates),來(lái)消除臨時(shí)對(duì)象以及合并循環(huán)是有可能的,并且不用修改上面的客戶端代碼的語(yǔ)法。最   后的程序使用了更少的內(nèi)存,而且運(yùn)行速度會(huì)有很大的提升。

  • 產(chǎn)生個(gè)性化的設(shè)計(jì)模式實(shí)現(xiàn)。像策略模式,觀察者模式,訪問(wèn)者模式等等這些設(shè)計(jì)模式能夠以很多方式被實(shí)現(xiàn)。使用基于模板的技術(shù)被叫做policy-based設(shè)計(jì),我們可以創(chuàng)建表示獨(dú)立設(shè)計(jì)選擇(choice或者叫”policies”)的 模板,這些模板可以以任意的方式進(jìn)行組合來(lái)產(chǎn)生個(gè)性化的模式實(shí)現(xiàn)。例如,使用這種技術(shù)能夠創(chuàng)建一些實(shí)現(xiàn)智能指針行為策略(policies)的模板,使用它能夠產(chǎn)生(在編譯期)上百種不同的智能指針類型。這項(xiàng)技術(shù)已經(jīng)超越了編程工藝領(lǐng)域,如設(shè)計(jì)模式和智能指針,它成為了生殖編程(generative programming)的基礎(chǔ)。

回到頂部

4. TMP現(xiàn)狀分析

TMP并不是為每個(gè)人準(zhǔn)備的。因?yàn)檎Z(yǔ)法不直觀,支持的相關(guān)工具也很弱。(像為模板元編程提供的調(diào)試器。)作為一個(gè)“突然性“的語(yǔ)言它只是最近才被發(fā)現(xiàn)的,TMP編程的一些約定正在實(shí)驗(yàn)階段。然而通過(guò)將工作從運(yùn)行時(shí)移到編譯期所帶來(lái)的效率提升帶給人很深刻的印象,對(duì)一些行為表達(dá)的能力(很難或者不可能在運(yùn)行時(shí)實(shí)現(xiàn))也是很吸引人的。

對(duì)于TMP的支持正在上升期。很可能下個(gè)版本的C++就是顯示的支持它。TR1中已經(jīng)支持了(Item 54)。關(guān)于這個(gè)主題的書籍已經(jīng)開始出來(lái)了,網(wǎng)上的一些關(guān)于TMP信息也越來(lái)越多。TMP可能永遠(yuǎn)不會(huì)成為主流,但是對(duì)于一些程序員來(lái)說(shuō)——尤其是程序庫(kù)的實(shí)現(xiàn)者——幾乎必然會(huì)成為主要手段。

回到頂部

5. 總結(jié)

  • 模板元編程可以將工作從運(yùn)行時(shí)移到編譯期,這樣可以更早的發(fā)現(xiàn)錯(cuò)誤,并且提高運(yùn)行時(shí)性能。

  • 基于策略選擇(policy choices)的組合TMP能夠被用來(lái)產(chǎn)生個(gè)性化的代碼,也能夠用來(lái)防止為特定類型生成不合適的代碼。


作者: HarlanC 

博客地址: http://www.cnblogs.com/harlanc/ 
個(gè)人博客: http://www.harlancn.me/ 
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出, 原文鏈接 

如果覺的博主寫的可以,收到您的贊會(huì)是很大的動(dòng)力,如果您覺的不好,您可以投反對(duì)票,但麻煩您留言寫下問(wèn)題在哪里,這樣才能共同進(jìn)步。謝謝! 

http://www.cnblogs.com/harlanc/p/6714004.html