# Item 48: 感受 template metaprogramming(模板元編程)
作者:Scott Meyers
譯者:fatalerror99 (iTePub's Nirvana)
發布:http://blog.csdn.net/fatalerror99/
template metaprogramming (TMP)(模板元編程)是寫 template-based(基于模板)的運行于編譯期間的 C++ 程序的過程。考慮一下:一個 template metaprogram(模板元程序)是用 C++ 寫的運行于 C++ 編譯器中的程序。當一個 TMP 程序運行完成,它的輸出——從 templates(模板)實例化出的 C++ 源代碼片斷——隨后被正常編譯。
如果你僅把它看作古怪的特性而沒有打動你,那你就不會對它有足夠的深入的思考。
C++ 并不是為 template metaprogramming(模板元編程)設計的,但是自從 TMP 在 1990 年代早期被發現以來,它已被證明非常有用,使 TMP 變容易的擴展很可能會被加入到語言和它的標準庫之中。是的,TMP 是被發現,而不是被發明。TMP 所基于的特性在 templates(模板)被加入 C++ 的時候就已經被引進了。所需要的全部就是有人注意到它們能夠以一種精巧的而且意想不到的方式被使用。
TMP 有兩個強大的力量。首先,它使得用其它方法很難或不可能的一些事情變得容易。第二,因為 template metaprograms(模板元程序)在 C++ 編譯期間執行,它們能將工作從運行時轉移到編譯時。一個結果就是通常在運行時才能被察覺的錯誤能夠在編譯期間被發現。另一個結果是使用了 TMP 的 C++ 程序在以下幾乎每一個方面都可能更有效率:更小的可執行代碼,更短的運行時間,更少的內存需求。(然而,將工作從運行時轉移到編譯時的一個結果就是編譯過程變得更長。使用 TMP 的程序可能比它們的 non-TMP 對等物占用長得多的編譯時間。)
考慮 228 頁引入的 STL 的 advance 的偽代碼。(在 Item 47。你現在可能需要讀那個 Item,因為在本 Item 中,我假設你已經熟悉了那個 Item 的內容。)就像 228 頁,我突出表示代碼中的偽代碼部分:
```
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if (iter is a random access iterator) {
iter += d; // use iterator arithmetic
} // for random access iters
else {
if (d >= 0) { while (d--) ++iter; } // use iterative calls to
else { while (d++) --iter; } // ++ or -- for other
} // iterator categories
}
```
我們可以用 typeid 把偽代碼變成真正的代碼。這就產生了一個解決此問題的“常規”的 C++ 方法——它的全部工作都在運行時做:
```
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if (typeid(typename std::iterator_traits<IterT>::iterator_category) ==
typeid(std::random_access_iterator_tag)) {
iter += d; // use iterator arithmetic
} // for random access iters
else {
if (d >= 0) { while (d--) ++iter; } // use iterative calls to
else { while (d++) --iter; } // ++ or -- for other
} // iterator categories
}
```
Item 47 指出這個 typeid-based(基于 typeid)的方法比使用 traits 的方法效率低,因為這個方法,(1)類型檢測發生在運行時而不是編譯期,(2)用來做運行時類型檢測的代碼必須出現在可執行代碼中。實際上,這個例子展示了 TMP 如何能比一個“常規”C++ 程序更高效,因為 traits 方法是 TMP。記住,traits 允許編譯時在類型上的 if...else 計算。
我先前談及一些事情在 TMP 中比在“常規”C++ 中更簡單,而 advance 提供了這方面的一個例子。Item 47 提到 advance 的 typeid-based(基于 typeid)的實現可能會導致編譯問題,而這就是一個產生問題的例子:
```
std::list<int>::iterator iter;
...
advance(iter, 10); // move iter 10 elements forward;
// won't compile with above impl.
```
考慮 advance 為上面這個調用生成的版本。用 iter 和 10 的類型取代 template parameters(模板參數)IterT 和 DistT 之后,我們得到這個:
```
void advance(std::list<int>::iterator& iter, int d)
{
if (typeid(std::iterator_traits<std::list<int>::iterator>::iterator_category) ==
typeid(std::random_access_iterator_tag)) {
iter += d; // error!
}
else {
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}
```
問題在突出顯示的行,使用了 += 的那行。在當前情況下,我們試圖在一個 list<int>::iterator 上使用 +=,但是 list<int>::iterator 是一個 bidirectional iterator(雙向迭代器)(參見 Item 47),所以它不支持 +=。只有 random access iterators(隨機訪問迭代器)才支持 +=。此時,我們知道我們永遠也不會試圖執行那個 += 行,因為那個 typeid 檢測對于 list<int>::iterators 永遠不成立,但是編譯器被責成確保所有源代碼是正確的,即使它不被執行,而當 iter 不是一個 random access iterator(隨機訪問迭代器)時 "iter += d" 是不正確的。traits-based(基于 traits)的 TMP 解決方案與此對比,那里針對不同類型的代碼被分離到單獨的函數中,其中每一個都只使用了可用于它所針對的類型的操作。
TMP 已經被證明是 Turing-complete(圖靈完備)的,這意味著它強大得足以計算任何東西。使用 TMP,你可以聲明變量,執行循環,編寫和調用函數,等等。但是這些結構看起來與其在“常規”C++ 中的樣子非常不同。例如,Item 47 展示了 if...else 條件在 TMP 中是如何通過 templates(模板)和 template specializations(模板特化)被表達的。但那是 assembly-level(匯編層次)的 TMP。針對 TMP 的庫(例如 Boost 的 MPL ——參見 Item 55)提供了一種更高層次的語法,雖然還不至于讓你把它誤認為是“常規”C++。
為了一窺其它東西在 TMP 中如何工作,讓我們來看看 loops(循環)。TMP 中沒有真正的 looping construct(循環結構),因此 loops(循環)的效果是通過 recursion(遞歸)完成的。(如果你對 recursion(遞歸)感到不舒服,在你斗膽進入 TMP 之前一定要解決它。TMP 很大程度上是一個 functional language(函數性語言),而 recursion(遞歸)之于 functional language(函數性語言)就像電視之于美國流行文化:是密不可分的。)然而,甚至 recursion(遞歸)都不是常規樣式的,因為 TMP loops 不涉及 recursive function calls(遞歸函數調用),它們涉及 recursive template instantiations(遞歸模板實例化)。
TMP 的 "hello world" 程序在編譯期間計算一個階乘。它不是一個很令人興奮的程序,不過,即使不是 "hello world",也有助于語言入門。TMP 階乘計算示范了通過 recursive template instantiation(遞歸模板實例化)實現循環。它也示范了在 TMP 中創建和使用變量的一種方法。看:
```
template<unsigned n> // general case: the value of
struct Factorial { // Factorial<n> is n times the value
// of Factorial<n-1>
enum { value = n * Factorial<n-1>::value };
};
template<> // special case: the value of
struct Factorial<0> { // Factorial<0> is 1
enum { value = 1 };
};
```
給出這個 template metaprogram(模板元程序)(實際上只是單獨的 template metafunction(模板元函數)Factorial),你可以通過引用 Factorial<n>::value 得到 factorial(n) 的值。
代碼的循環部分出現在 template instantiation(模板實例化)Factorial<n> 引用 template instantiation(模板實例化)Factorial<n-1> 的地方。就像所有正確的 recursion(遞歸)有一個導致遞歸結束的特殊情況。這里,它就是 template specialization(模板特化)Factorial<0>。
Factorial template 的每一個 instantiation(實例化)都是一個 struct,而每一個 struct 都使用 enum hack(參見 Item 2)聲明了一個名為 value 的 TMP 變量。value 用于持有階乘計算的當前值。如果 TMP 有一個真正的循環結構,value 會在每次循環時更新。因為 TMP 在循環的位置使用 recursive template instantiation(遞歸模板實例化),每一個 instantiation(實例化)得到它自己的 value 的拷貝,而每一個拷貝擁有適合于它在“循環”中所處的位置的值。
你可以像這樣使用 Factorial:
```
int main()
{
std::cout << Factorial<5>::value; // prints 120
std::cout << Factorial<10>::value; // prints 3628800
}
```
如果你覺得這比吃了冰淇淋還涼快,你就具有了一個 template metaprogrammer(模板元程序員)應有的素質。如果 templates(模板)以及 specializations(特化)以及 recursive instantiations(遞歸實例化)以及 enum hacks 以及對類似 Factorial<n-1>::value 這樣的類型的需要使你毛骨悚然,好吧,你是一個不錯的常規 C++ 程序員。
當然,Factorial 示范的 TMP 的效用大約就像 "hello world" 示范的任何常規編程語言的效用一樣。為了領會為什么 TMP 值得了解,更好地理解它能做什么是很重要的。這里是三個示例:
* Ensuring dimensional unit correctness(確保計量單位正確性)。在科學和工程應用中,計量單位(例如,質量,距離,時間,等等)被正確組合是基礎。例如,將一個代表質量的變量賦值給一個代表速度的變量是一個錯誤,但是用一個時間變量去除距離變量并將結果賦給一個速度變量就是正確的。使用 TMP,不論計算多么復雜,確保(在編譯期間)一個程序中所有計量單位組合都是正確的是有可能的。(這是一個如何用 TMP 進行早期錯誤診斷的例子。)這個 TMP 的使用的一個有趣的方面是能夠支持分數指數。這需要這個分數在編譯期間被簡化以便于編譯器能夠確認,例如,單位 time1/2 與單位 time4/8 是相同的。
* Optimizing matrix operations(優化矩陣操作)。Item 21 闡釋了一些函數,包括 operator\*,必須返回新的 objects,而 Item 44 引入了 SquareMatrix class,所以考慮如下代碼:
```
typedef SquareMatrix<double, 10000> BigMatrix;
BigMatrix m1, m2, m3, m4, m5; // create matrices and
... // give them values
BigMatrix result = m1 * m2 * m3 * m4 * m5; // compute their product
```
用“常規”方法計算 result 需要四個臨時矩陣的創建,用于每一次調用 operator\* 的結果。此外,獨立的乘法產生了一個四次循環遍歷矩陣元素的序列。使用一種與 TMP 相關的被稱為 expression templates(表達式模板)的高級模板技術,完全不改變上面的客戶代碼的語法,而消除臨時對象以及合并循環是有可能的。最終的軟件使用更少的內存而且運行速度戲劇性地更快。
* Generating custom design pattern implementations(生成自定義的設計模式實現)。像 Strategy(參見 Item 35),Observer,Visitor 等設計模式能用很多方法實現。使用一種被稱為 policy-based design(基于 policy 設計)的 TMP-based(基于 TMP)的技術,使得創建代表獨立的設計選擇的 templates ("policies") 成為可能,這種 templates 能以任意的方法組合以產生帶有自定義行為的模式實現。例如,這種技術經常用于允許幾個實現了 smart pointer behavioral(智能指針行為)的 policies 的 templates 生成(在編譯期間)數百個不同的 smart pointer(智能指針)類型。將類似設計模式和智能指針這樣的編程器件的范圍大大地擴展,這項技術是通常所說的 generative programming(產生式編程)的基礎。
TMP 并不適合于每一個人。它的語法是不符合直覺的,工具支持也很弱(template metaprograms 的調試器?哈!)作為一個相對晚近才發現的“附屬”語言,TMP programming 的規則仍然帶有試驗性質。然而,通過將工作從運行時轉移到編譯時所提供的效率提升還是能給人留下深刻的印象,而表達在運行時很難或不可能實現的行為的能力也相當有吸引力。
TMP 的支持程度在不斷提升。很可能在 C++ 的下一個版本中將對它提供直接的支持,而且 TR1 已經這樣做了(參見 Item 54)。關于這一主題的書籍也即將開始出版(目前,C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond 已經出版——譯者注),而 web 上的 TMP 信息也正在保持增長。TMP 也許永遠不會成為主流,但是對于某些程序員——特別是庫開發者——它幾乎必然會成為主料。
Things to Remember
* template metaprogramming(模板元編程)能將工作從運行時轉移到編譯時,這樣就能夠更早察覺錯誤并提高運行時性能。
* TMP 能用于在 policy choices 的組合的基礎上生成自定義代碼,也能用于避免為特殊類型生成不適當的代碼。
- Preface(前言)
- Introduction(導言)
- Terminology(術語)
- Item 1: 將 C++ 視為 federation of languages(語言聯合體)
- Item 2: 用 consts, enums 和 inlines 取代 #defines
- Item 3: 只要可能就用 const
- Item 4: 確保 objects(對象)在使用前被初始化
- Item 5: 了解 C++ 為你偷偷地加上和調用了什么函數
- Item 6: 如果你不想使用 compiler-generated functions(編譯器生成函數),就明確拒絕
- Item 7: 在 polymorphic base classes(多態基類)中將 destructors(析構函數)聲明為 virtual(虛擬)
- Item 8: 防止因為 exceptions(異常)而離開 destructors(析構函數)
- Item 9: 絕不要在 construction(構造)或 destruction(析構)期間調用 virtual functions(虛擬函數)
- Item 10: 讓 assignment operators(賦值運算符)返回一個 reference to *this(引向 *this 的引用)
- Item 11: 在 operator= 中處理 assignment to self(自賦值)
- Item 12: 拷貝一個對象的所有組成部分
- Item 13: 使用對象管理資源
- Item 14: 謹慎考慮資源管理類的拷貝行為
- Item 15: 在資源管理類中準備訪問裸資源(raw resources)
- Item 16: 使用相同形式的 new 和 delete
- Item 17: 在一個獨立的語句中將 new 出來的對象存入智能指針
- Item 18: 使接口易于正確使用,而難以錯誤使用
- Item 19: 視類設計為類型設計
- Item 20: 用 pass-by-reference-to-const(傳引用給 const)取代 pass-by-value(傳值)
- Item 21: 當你必須返回一個對象時不要試圖返回一個引用
- Item 22: 將數據成員聲明為 private
- Item 23: 用非成員非友元函數取代成員函數
- Item 24: 當類型轉換應該用于所有參數時,聲明為非成員函數
- Item 25: 考慮支持不拋異常的 swap
- Item 26: 只要有可能就推遲變量定義
- Item 27: 將強制轉型減到最少
- Item 28: 避免返回對象內部構件的“句柄”
- Item 29: 爭取異常安全(exception-safe)的代碼
- Item 30: 理解 inline 化的介入和排除
- Item 31: 最小化文件之間的編譯依賴
- Item 32: 確保 public inheritance 模擬 "is-a"
- Item 33: 避免覆蓋(hiding)“通過繼承得到的名字”
- Item 34: 區分 inheritance of interface(接口繼承)和 inheritance of implementation(實現繼承)
- Item 35: 考慮可選的 virtual functions(虛擬函數)的替代方法
- Item 36: 絕不要重定義一個 inherited non-virtual function(通過繼承得到的非虛擬函數)
- Item 37: 絕不要重定義一個函數的 inherited default parameter value(通過繼承得到的缺省參數值)
- Item 38: 通過 composition(復合)模擬 "has-a"(有一個)或 "is-implemented-in-terms-of"(是根據……實現的)
- Item 39: 謹慎使用 private inheritance(私有繼承)
- Item 40: 謹慎使用 multiple inheritance(多繼承)
- Item 41: 理解 implicit interfaces(隱式接口)和 compile-time polymorphism(編譯期多態)
- Item 42: 理解 typename 的兩個含義
- Item 43: 了解如何訪問 templatized base classes(模板化基類)中的名字
- Item 44: 從 templates(模板)中分離出 parameter-independent(參數無關)的代碼
- Item 45: 用 member function templates(成員函數模板) 接受 "all compatible types"(“所有兼容類型”)
- Item 46: 需要 type conversions(類型轉換)時在 templates(模板)內定義 non-member functions(非成員函數)
- Item 47: 為類型信息使用 traits classes(特征類)
- Item 48: 感受 template metaprogramming(模板元編程)
- Item 49: 了解 new-handler 的行為
- Item 50: 領會何時替換 new 和 delete 才有意義
- Item 51: 編寫 new 和 delete 時要遵守慣例
- Item 52: 如果編寫了 placement new,就要編寫 placement delete
- 附錄 A. 超越 Effective C++
- 附錄 B. 第二和第三版之間的 Item 映射