首页 > 编程语言 > 详细

《Effective C++》:条款48:认识template元编程

时间:2015-03-14 23:17:02      阅读:409      评论:0      收藏:0      [点我收藏+]

Template metaprogramming(TMP,模板元编程)是编写template-based C++程序,编译的过程。template metaprogramming是用C++写的模板程序,编译器编译出具体化的过程。也就是说,TMP程序执行后,从templates具体化出来C++源码,不再是模板了。

TMP有两个作用,一是它让某些事更容易。例如编写STL容器,使用模板,可是存放任何类型元素。二是将执行在运行期的某些工作转移到了编译期。还有一个结果是使用TMP的C++程序可能在其他方面更高效:较小的可执行文件、较短的运行期、较少的内存需求。但是将运行期的工作转移到了编译期,编译期可能变长了。

再看一下条款 47中的advance伪码

 template<typename Iter, typename DistT>
    void advance(IteT& iter,DistT d)
    {
        if(iter is a random access iterator)
            iter+=d;
        else
        {
            if(d>=0)
                while(d--) ++iter;
            else 
                while(d++) --iter;
        }
    }

可以使用typeid让判断iter类型的伪码运行

 template<typename Iter, typename DistT>
    void advance(IteT& iter,DistT d)
    {
        if(typeid(typename std::iterator_traits<IterT>::iterator_category)
        ==typeid(std::random_access_iterator_tag))
            iter+=d;
        else
        {
            if(d>=0)
                while(d--) ++iter;
            else 
                while(d++) --iter;
        }
    }

typeid-based解法效率比traits解法低,因为在此方案中,1类型测试发生在运行期而不是编译期,2运行期类型测试代码在(或被连接于)可执行文件中。这个例子可以说明TMP比正常的C++程序更高效,因为traits解法就是TMP。

一些东西在TMP比在正常的C++更容易,advance提供一个好例子。advance的typeid-based实现方式可能导致编译期问题

    std::list<int>::iterator iter;
    ……
    advance(iter,10);
    void advance(std::list<int>::iterator& iter,int d)
    {
        if(typeid(typename std::iterator_traits<std::list<int>::iterator>::iterator_category)
        ==typeid(std::random_access_iterator_tag))
            iter+=d;//错误
        else
        {
            if(d>=0)
                while(d--) ++iter;
            else 
                while(d++) --iter;
        }
    }

在+=这个操作符上是错误调用。因为list::iterator不支持+=,它是bidirectional迭代器。我们知道不会执行+=那一行,因为typeid那一行总是不相等;但是编译期要确保所有源码都有效,即使是不会执行的代码。traits-based TMP解法针对不同类型执行不同代码,不会出现上述问题。

TMP已被证明是个图灵完全机器,也就是说它的威力足以计算任何事物。可以使用TMP声明变量、执行循环、编写调用函数……。有时候这会和正常C++对应物看起来很是不同,例如条款 47展示的TMP if-else是由templas和其特化具体表现出来。不过那是汇编语言级的TMP。针对TMP设计的程序库(例如Boost’s MPL,**条款**55)提供更高级的语法。

为了再次认识下事物在TMP中如何运作,来看下循环。TMP没有真正循环,循环由递归(recursion)完成。TMP递归甚至不是正常的递归,因为TMP递归不涉及递归函数调用,而是涉及递归模板化(recursive template instantiation)。

TMP的起手程序是在编译期计算阶乘。TMP的阶乘运输示范如何通过递归模板具体化实现循环,以及如何在TMP中创建和使用变量

    template<unsigned n>
    struct Factorial{
        enum {value=n*Factorial<n-1>::value};
    };
    template<>
    struct Factorial<0>{ //特殊情况,Factorial<0>的值是1
        enum {value=1};
    };

有了这个template metaprogram,只要指涉Factorial::value就可以得到n阶乘值。循环发生在template具体化Factorial内部指涉另一个template具体化Factorial之时。特殊情况的template特化版本Factorial<0>是递归的结束。

每个Factorial template具体化都是一个struct,每个struct都声明一个名字为value的TMP变量,用来保存当前计算所获得的阶乘值。TMP以递归模板具体化取代循环,每个具体化有自己一份value,每个value有其循环内适当值。

用Factorial示范TMP就像用hello world示范编程语言一样。为了领悟TMP之所以值得学习,就要先对它能够达成什么目标有一个比较好的理解。下面举三个例子:

  • 确保量度单位正确。使用TMP就可以确保在编译期所有量度单位的组合都正确。
  • 优化矩阵运算。条款 21曾经提到过某些函数包括operator * 必须返回新对象,在条款 44中有一个SquareMatrix。如果这样使用
    typedef SquareMatrix<double,1000> BigMatrix;
    BigMatrix m1,m2,m3,m4,m5;
    ……
    BigMatrix result=m1 * m2 * m3 * m4 * m5;

上面乘法会产生四个临时性矩阵,乘法还可能产生了4个作用在矩阵元素身上的循环。如果使用高级、与TMP相关的template(即expression templates),就有可能消除那些临时对象并合并循环。所以TMP使用较少内存,执行速度也有提升。

  • 可以生成客户定制之设计模式(custom design pattern)实现品。使用policy-based design之TMP-based技术,有可能产生一些templates用来表述独立的设计项(所谓policies),然后可以任意结合它们,导致模式实现品带着客户定制的行为。

TMP目前还不完全成熟,语法不直观,支持的工具还不充分。但TMP对难以或甚至不可能于运行期实现出来的行为表现能力很吸引人。虽然TMP不会成为主流,但是会成为一些程序员(特别是程序库的开发人员)的主要粮食。

总结

  • Template metaprogramming(TMP,模板元编程)可将工作由运行期移到编译期,因而得以实现早期错误侦测和更高的执行效率。
  • TMP可被用来生成“基于政策选择组合”(based on combinations of policy choices)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。

《Effective C++》:条款48:认识template元编程

原文:http://blog.csdn.net/kangroger/article/details/44264053

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!