模板能夠被用作預編譯程序,Todd Veldhuizen和David Vandevoorde指出,任何算法都能被模板化,算法的輸入參數在編譯期提供。只要有好的編譯器,中間代碼能夠徹底優化掉。算法
斐波拉契數列,老生常談啦,一開始學遞歸就學這個東西,一般下面這種方法都是明令禁止的:編程
unsigned int fib(unsigned int n) {
if (n == 0 || n == 1)
{
return 1 ;
}
else
{
return fib( n - 1) + fib (n - 2);
}
}
緣由很簡單,它在運行的時候會不停壓棧,容易引發棧溢出的狀況。markdown
可是有一種辦法能夠適用模板元編程來進行優化。不少人其實不知道模板能夠做爲虛擬編譯程序,能夠快速大量地建立優化代碼。函數
此外,因爲算法的輸入參數是在編譯期提供的,所以不會在runtime的時候進行重複的操做,這樣一來能夠達到很是高的效率。優化
那麼該如何進行優化以上代碼?ui
template <unsigned int N>
struct FibR
{
enum
{
Val = FibR< N-1 >:: Val + FibR::Val
};
};
template <>
struct FibR <0>
{
enum
{
Val = 1
};
};
template <>
struct FibR <1>
{
enum
{
Val = 1
};
};
#define fib(n) FibR::Val
這樣一來,咱們能夠經過#define來調用這個模板。spa
std::cout << fib (4) << std::endl;
須要注意的是,模板函數實際上不是真正的函數——它其實是一個枚舉整數,在編譯期遞歸生成。語句Val = FibR< N-1 >:: Val + FibR::Val
雖然不是很常見,可是是徹底合法的。code
FibR定義爲一個Struct,是由於它的數據默認都是public的。而Val採用枚舉整數的緣由是它能夠預先就指定它的Value。遞歸
固然,有遞歸,固然就要有結束條件。在模板中處理基本狀況的方法就是使用模板特化(template specialization)。遊戲
凡是由template <>標記的,就意味着這是模板特化。那麼對於fib(4)來講,編譯器是這麼玩的:
fib (4)
= FibR< 4 >::Val
= FibR< 3 >::Val + FibR< 2 >::Val
= FibR< 2 >::Val + FibR< 1 >::Val + FibR< 1 >::Val + FibR< 0 >::Val
= FibR< 0 >::Val + FibR< 1 >::Val + FibR< 1 >::Val + FibR< 1 >::Val + FibR< 0>:: Val
= 1 + 1 + 1 + 1 + 1
= 5
注意這是編譯器玩的東西,全部的輸入都在編譯期間肯定,所以在最終,編譯器生成的代碼就是:
std::cout << 5;
這種方法是C++中的一種頗有用的方法。有些時候對於某些指數級的運行時間的函數,死都不能降爲常數級運行時,能夠考慮使用這種編程方式。
這樣一來就能夠經過增長額外的編譯時間來下降程序的執行時間。固然對於遊戲來講,執行時間確定比編譯時間重要。
一般的作法是:
unsigned int fact (n )
{
return n <= 1 ? 1 : n * fact( n - 1);
}
可是若是使用模板元編程,那麼代碼就是:
template < unsigned int N >
struct FactR
{
enum {
Val = N * FactR::Val
};
};
template <>
struct FactR < 1 >
{
enum
{
Val = 1
};
};
#define fact(n) FactR::Val
就和斐波拉契數列同樣,編譯器會將最終的運算調用進行換算,也就是說降成了常數級的運行時間,這就是使用元編程的好處。
模板元編程固然也存在一些缺點:
模板元編程雖然頗有意思,並且很高效,可是說實話在項目中,這種東西用的真的特別多嗎(注:這篇博客寫於2015年8月,如今能夠回答這個問題了:在遊戲中其實對於矩陣乘法等操做均可以用到模板元編程,所以它仍是頗有必要去掌握的)?我不由陷入了沉思的大波之中……