48:认识template元编程

模板元编程(TMP)是一种在C++中执行于编译期的编程技术,能提高代码效率,提前发现错误。TMP允许在编译时执行某些任务,生成更小、运行更快的代码,但也可能导致编译时间增加。文章举例说明了TMP在处理迭代器类型和矩阵运算中的应用,以及如何用于创建客户定制的设计模式实现。
摘要由CSDN通过智能技术生成

template metaprogramming(TMP,模板元编程)是编写template-based C++程序并执行于编译期的过程。

所谓template metaprogram(模板元程序)是以C++写成、执行于C++编译器内的程序。一旦TMP程序结束执行,其输出,也就是从template具现出的若干C++源码,便会一如以往地被编译。

TMP有两个伟大的效力。

第一,它让某些事情更容易。如果没有它,那些事情将是困难的,甚至不可能的。

第二,由于模板元程序执行于C++编译期,因此可将工作从运行期转移到编译期。

这导致的一个结果是,某些错误原本通常在运行期才能侦测到,现在可在编译期找出来。另一个结果是,使用TMP的C++程序可能在每一方面都更高效:较小的可执行文件、较短的运行期、较少的内存需求。然而将工作从运行期转移至编译期的另一个结果是,编译时间变长了。程序若使用TMP,其编译时间可能远长于不适用TMP的对应版本。

考虑在文章47的一段伪码:

template<typename IterT,typename DistT>
void advance(IterT& iter, DistT d)
{
    if (iter is a random access iterator)
        iter += d;//针对random access迭代器使用迭代器算术运算
    else
    {
        //针对其他迭代器分类反复调用++或--
        if (d >= 0)
        {
            while (--d) ++iter;
        }
        else
        {
            while (++d) --iter;
        }
    }
}

可以使用typeid让其中的伪码成真:

template<typename IterT,typename DistT>
void advance(IterT& iter, DistT d)
{
    if (typeid(typename std::iterator_traits<IterT)::iterator_category)
        ==typeid(std::random_access_iterator_tag)
        iter += d;//针对random access迭代器使用迭代器算术运算
    else
    {
        //针对其他迭代器分类反复调用++或--
        if (d >= 0)
        {
            while (--d) ++iter;
        }
        else
        {
            while (++d) --iter;
        }
    }
}

这个typeid-based解法的效率比traits解法低,因为在此方案中,1.类型测试发生于运行期而非编译期,2.“运行期类型测试”代码会出现在(或者说被连接于)可执行文件中。

advance的typeid-based实现方式可能导致编译期问题。

例如:

std::list<int>::iterator iter;
//...
//移动iter向前走10个元素,无法通过编译
advance(iter, 10);

下面的一段代码便是针对上述调用而产生的。将template参数IterT和DistT分别替换为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<int>::iterator是bidirectional迭代器,并不支持+=。只有random access迭代器才支持+=。

此刻我们知道绝不会执行起+=那一行,因为测试typeid的那一行总是会因为list<int>::iterator而失败,但编译器必须确保所有源码都有效,纵使是不会执行起来的代码。而当iter不是random access迭代器时,"iter+=d"无效。与此对比的是traits-based TMP解法,其针对不同类型而进行的代码,被拆分为不同的函数,每个函数所使用的操作(操作符)都可施行于该函数所对付的类型。

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

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

有了这个模板元程序,只要你指涉Factorial<n>::value就可以得到n阶乘值。

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

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

可以这样使用Factorial:

int main()
{
    std::cout << Factorial<5>::value;//印出120
    std::cout << Factorial<10>::value;//印出3628800
    return 0;
}

TMP的应用:

1.确保量度单位正确。

若使用TMP,可以确保(在编译期)程序中所有量度单位的组合都正确,不论其计算多么复杂。

2.优化矩阵运算。

考虑以下代码:

typedef SquareMatrix<double, 10000> BigMatrix;
BigMatrix m1, m2, m3, m4, m5;//创建矩阵
//...//赋予它们数值
BigMatrix result = m1 * m2 * m3 * m4*m5;//计算它们的乘积

以“正常的”函数调用动作来计算result,会创建4个暂时性矩阵,每一个用来存储对operator*的调用结果。各自独立的乘法产生了4个作用于矩阵元素身上的循环。若使用高级、与TMP相关的template技术,即所谓expression template,就有可能消除那些临时对象并合并循环,这一切都无需改变客户端的用法。于是TMP软件使用较少的内存,执行速度又有了戏剧性的提升。

3.可以生成客户定制的设计模式实现品。

设计模式如Strategy,Observer,Visitor等等都可以多种方式实现出来。运用所谓policy-based design的TMP-based技术,有可能产生一些template用来表述独立的设计选项,然后可以任意结合它们,导致模式实现品带着客户定制的行为。

总结

1.模板元编程可将工作由运行期移至编译期,因而得以实现早期错误侦测和更高的执行效率。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值