分享

认识模板元编程

 笔录收藏 2016-04-29

模板元编程是编写基于模板的C++程序并执行于编译期的过程。即模板元编程是以C++写成、执行于C++编译器内的程序。一旦模板元编程程序结束执行,其输出,也就是从模板具现出来的若干C++源码,便会一如既往地被编译。

  模板元编程有两个伟大的效力。第一,它让某些事情更容易。如果没有它,那些事情将是困难的,甚至是不可能的。第二,由于模板元编程执行于C++编译期,因此可将工作(如:错误检查)从运行期转移到编译器。还能让C++程序在每一方面都更高效:较小的可执行文件、较短的运行期、较少的内存需求。

  看前一篇blog中的代码:

template<typename IterT,typename DistT>

void advance(IterT& iter,DistT d)

{

   if(iter is a random access iterator)

     iter += d;

   else

     {

        if(d >= 0){while(d--) ++iter;}

        else {while(d++) --iter;}

     }

}

我们可以使用typeid让其中的伪码成真,取得C++对此问题的一个“正常”解决方案——所有工作都在运行期进行:

template<typename IterT,typename DistT>

void advance(IterT& iter,DistT d)

{

   if(typeid(typename std::iterator_traits<IterT>::iterator_catagory)

         == typeid(std::random_access_iterator_tag))

     iter += d;

   else

     {

        if(d >= 0){while(d--) ++iter;}

        else {while(d++) --iter;}

     }

}

   在上一篇blog中说过,这个typeid-based解法的效率比traits解法低,因为在此方案中(1)类型测试发生于运行期而非编译期,(2)“运行期类型测试”代码会出现在(或被链接于)可执行文件中。实际上这个例子正可彰显模板元编程如何能够比“正常的”C++程序更高效,因为traits解法就是模板元编程。

   在上一篇中说到advance的typeid-based实现方式可能导致编译期问题,下面就是例子:

list<int>::iterator iter;

...

advance(iter,10);   //移动iter向前走10个元素,这个代码无法通过编译

下面这一版advance便是针对上述调用而产生的。将模板参数IterT和DistT分别替换为iter和10的类型之后,我们得到:

void advance(list<int>::iterator& iter,int d)

{

   if(typeid(typename std::iterator_traits<list<int>::iterator>::iterator_catagory)

         == typeid(std::random_access_iterator_tag))

     iter += d;

   else

     {

        if(d >= 0){while(d--) ++iter;}

        else {while(d++) --iter;}

     }

}

   问题出在+=操作符,那是尝试在一个list<int>::iterator身上使用+=,但list<int>::iterator是双向迭代器,并不支持+=。只有随机访问迭代器才支持+=。此刻我们绝不会执行起+=那一行,因为测试typeid的那一行总是会因为list<int>::iterator而失败,但编译器必须确保所有源码有效,纵使是不会执行起来的代码!而当iter不是随机访问迭代器时“iter+=d”无效。与此对比的是基于traits模板元编程解法,其针对不同类型而进行的代码,被拆分为不同的函数,每个函数所使用的操作(操作符)都可施行于该函数所对付的类型。

   模板元编程(TMP)已被证明是个“图灵完全”机器,意思是它的威力大到足以计算任何事物,使用TMP可以声明变量、执行循环、编写及调用函数。

   让我们看看TMP中的循环。TMP并没有真正的循环构件,所以循环效果由递归完成。TM主要是个“函数式语言”。TMP的递归甚至不是正常种类,因为TMP循环并不涉及递归函数调用,而是涉及“递归模板具现化”。

   TMP的阶乘运算示范如何通过“递归模板具现化”实现循环,以及如何在TMP中创建和使用变量。

template<unsigned n>

struct Factorial                 //一般情况:Factorial<n>的值是n乘以Factorial<n-1>的值

{

   enum{value = n * Factorial<n-1>::value};

};

template<>

struct Factorial<0>            //特殊情况:Factorial<0>的值是1

{

   enum{value = 1};

};

   有了这个模板元编程(其实只是个单一的模板元函数Factorial),只要你指涉Factorial<n>::value就可以得到n阶乘的值。

   循环发上在模板具现体Factorial<n>内部指涉另一个模板具现体Factorial<n-1>之时。和所有良好递归一样,我们需要一个特殊情况造成递归结束,这里的特殊情况是template特化体Factorial<0>。

   每个Factorial模板具现体都是一个struct,每个struct都使用enum hack声明一个名为value的TMP变量,vaule用来保存当前计算所得的阶乘值。如果TMP拥有真正的循环构件,value应该在每次循环内获得更新。但由于TMP系以“递归模板具现化”取代循环,每个具现体有自己的一份value,而每个value有其循环内的适当值。

   现在可以这样使用Factorial(VC++6.0测试通过):

int main()

{

   cout<<Factorial<5>::value;          //打印出120

   cout<<Factorial<10>::value;         //打印出3628800

}

 

TMP能够达到的目标:

(1)确保亮度单位正确。如可确保将距离除以时间的结果给速度等。

(2)优化矩阵计算。例如能够减少临时矩阵的生成等。

(3)可以生成客户定制之设计模式实现品。

 

总结:(1)TMP可将工作由运行期移往编译器,因而得以实现早期错误侦查和更高的执行效率。

 (2)TMP可被用来生成“基于政策选择组合”的客户定制代码,也可以用来避免生成对某些特殊类型并不适合的代码。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多