zoukankan      html  css  js  c++  java
  • 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); 
        }
    }

    原因很简单,它在运行的时候会不停压栈,容易引起栈溢出的情况。

    但是有一种办法可以适用模板元编程来进行优化。很多人其实不知道模板可以作为虚拟编译程序,可以快速大量地创建优化代码。

    此外,由于算法的输入参数是在编译期提供的,因此不会在runtime的时候进行重复的操作,这样一来可以达到非常高的效率。

    那么该如何进行优化以上代码?

    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来调用这个模板。

    std::cout << fib (4) << std::endl;

    需要注意的是,模板函数实际上不是真正的函数——它实际上是一个枚举整数,在编译期递归生成。语句Val = FibR< N-1 >:: Val + FibR::Val虽然不是很常见,但是是完全合法的。

    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月,现在可以回答这个问题了:在游戏中其实对于矩阵乘法等操作都可以用到模板元编程,因此它还是很有必要去掌握的)?我不禁陷入了沉思的大波之中……

  • 相关阅读:
    (转载)机器学习方法的PPT
    算法的力量(转李开复)
    CNKI免费帐号
    图像增强(二)
    初始化 Microsoft Visual SourceSafe 源代码管理提供程序时失败。您无法使用此提供程序执行源代码管理操作。”
    2012年"浪潮杯"山东省第三届ACM大学生程序设计竞赛 Fruit Ninja I
    hdu 3607 Traversal
    zoj 3686 A Simple Tree Problem
    hdu 3727 Jewel
    hdu 4366 Successor
  • 原文地址:https://www.cnblogs.com/arrowinmyknee/p/5470390.html
Copyright © 2011-2022 走看看