C++ —— 使用模板元編程來進行遞歸運算的優化

模板能夠被用作預編譯程序,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月,如今能夠回答這個問題了:在遊戲中其實對於矩陣乘法等操做均可以用到模板元編程,所以它仍是頗有必要去掌握的)?我不由陷入了沉思的大波之中……

相關文章
相關標籤/搜索