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