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月,如今能够回答这个问题了:在游戏中其实对于矩阵乘法等操做均可以用到模板元编程,所以它仍是颇有必要去掌握的)?我不由陷入了沉思的大波之中……

相关文章
相关标签/搜索