1. TMP是什么?

模板元编程(template metaprogramming TMP)是实现基于模板的C++程序的过程,它能够在编译期执行。你可以想一想:一个模板元程序是用C++实现的并且可以在C++编译器内部运行的一个程序,它的输出——从模板中实例化出来的C++源码片段——会像往常一样被编译。

2. 使用TMP的优势

如果这没有冲击到你,是因为你没有足够尽力去想。

C++不是为了模板元编程而设计的,但是自从TMP早在1990年被发现之后,它就被证明是非常有用的,为了使TMP的使用更加容易,在C++语言和标准库中加入了一些扩展。是的,TMP是被发现的,而不是被发明。当模板被添加到C++中的时候TMP这个特性就被引入了。对于某些人来说所有需要做的就是关注如何以一种聪明的和意想不到的方式来使用它。

TMP有两种强大的力量。第一,它使得一些事情变得容易也即是说如果没有TMP,这些事情做起来很难或者不可能实现第二,因为模板元编程在C++编译期执行,它们可以将一些工作从运行时移动到编译期。一个结果就是一些原来通常在运行时能够被发现的错误,现在在编译期就能够被发现了。另外一个结果就是使用TMP的C++程序在基本上每个方面都更加高效:更小的执行体,更短的运行时间,更少的内存需求。(然而,将工作从运行时移到编译期的一个后果就是编译时间增加了。使用TMP的程序比没有使用TMP的程序可能消耗更长的时间来进行编译。)

3. 如何使用TMP?

3.1 再次分析Item 47中的实例

考虑在Item 47中为STL的advance写出来的伪代码。我已经为伪代码部分做了粗体:

 template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if (iter is a random access iterator) { iter += d; // use iterator arithmetic } // for random access iters else { if (d >= ) { while (d--) ++iter; } // use iterative calls to
else { while (d++) --iter; } // ++ or -- for other
} // iterator categories
}

我们可以使用typeid替换伪代码,让程序能够执行。这就产生了一个“普通的”C++方法——也就是所有工作都在运行时开展的方法:

 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; // use iterator arithmetic } // for random access iters else { if (d >= ) { while (d--) ++iter; } // use iterative calls to
else { while (d++) --iter; } // ++ or -- for other
} // iterator categories
}

Item 47指出这种基于typeid的方法比使用trait效率更低,因为通过使用这种方法,(1)类型测试发生在运行时而不是编译期(2)执行运行时类型测试的代码在运行的时候必须可见。事实上,这个例子也展示出了为什么TMP比一个“普通的”C++程序更加高效,因为traits方式属于TMP。记住,trait使得在类型上进行编译期if…else运算成为可能。

我已经在前面提到过一些东西说明其在TMP中比在“普通”C++中更加容易,Item 47中也提供了一个advance的例子。Item 47中提到了advance的基于typeid的实现会导致编译问题,看下面的例子:

 std::list<int>::iterator iter;
...
advance(iter, ); // move iter 10 elements forward;
// won’t compile with above impl.

考虑为上面调用所产生的advance的版本,将模板参数IterT和DistT替换为iter和10的类型之后,我们得到下面的代码:

 void advance(std::list<int>::iterator& iter, int d)
{
if (typeid(std::iterator_traits<std::list<int>::iterator>::iterator_category) ==
typeid(std::random_access_iterator_tag)) { iter += d;

// error! won’t compile }
else {
if (d >= ) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}

有问题的是高亮部分,就是使用+=的语句。在这个例子中,我们在list<int>::iterator上使用+=,但是list<int>::iterator是一个双向迭代器(见Item 47),所以它不支持+=。只有随机访问迭代器支持+=。现在,我们知道了+=这一行将永远不会被执行到,因为为list<int>::iteraotr执行的typeid测试永远都不会为真,但是编译器有责任确保所有的源码都是有效的,即使不被执行到,当iter不是随机访问迭代器“iter+=d”就是无效代码。将它同基于tratis的TMP解决方案进行比较,后者把为不同类型实现的代码分别放到了不同的函数中,每个函数中进行的操作只针对特定的类型。

3.2 TMP是图灵完全的

TMP已经被证明是图灵完全的(Turing-Complete),这也就意味着它足够强大到可以计算任何东西。使用TMP,你可以声明变量,执行循环,实现和调用函数等等。但是这些概念同“普通”C++相对应的部分看起来非常不同。例如,Item 47中if…else条件在TMP中是如何通过使用模板和模板特化来表现的。但这是程序级别(assembly-level)的TMP。TMP库(例如,Boost MPL,见Item 55)提供了更高级别的语法,这些语法不会让你误认为是“普通的”C++。

3.3 TMP中的循环通过递归来实现

再瞥一眼事情在TMP中是如何工作的,让我们看一下循环。TMP中没有真正的循环的概念,所以循环的效果是通过递归来完成的。(如果一提到递归你就不舒服,在进入TMP 冒险之前你就需要处理好它。TMP主要是一个函数式语言,递归对于函数式语言就如同电视对美国流行文化一样重要:它们是不可分割的。)即使是递归也不是普通的递归,因为TMP循环没有涉及到递归函数调用,所涉及到的是递归模板实例化(template instantiations)。

TMP的“hello world”程序是在编译期计算阶乘。它算不上是令人激动的程序,“hello world”也不是,但是这两个例子对于介绍语言都是有帮助的。TMP阶乘计算通过对模板实例进行递归来对循环进行示范。也同样示范了变量是如何在TMP中被创建和使用的,看下面的代码:

 template<unsigned n>          // general case: the value of

 struct Factorial {                   // Factorial<n> is n times the value

 // of Factorial<n-1>
enum { value = n * Factorial<n->::value };
};
template<> // special case: the value of
struct Factorial<> { // Factorial<0> is 1
enum { value = };
};

考虑上面的模板元编程(真的仅仅是单一的元函数Factorial),你通过引用Factorial<n>::value来得到factorial(n)的值。

代码的循环部分发生在模板实例Factorial<n>引用模板实例Factorial<n-1>的时候。像所有递归一样,有一种特殊情况来让递归终止。在这里是模板特化Factorial<0>。

每个Factorial模板的实例都是一个结构体,每个结构体使用enum hack(Item 2)来声明一个叫做value的TMP变量。Value持有递归计算的当前值。如果TMP有一个真正的循环结构,value将会每次循环的时候进行更新。既然TMP使用递归模板实例来替换循环,每个实例会得到它自己的value的拷贝,每个拷贝都会有一个和“循环”中位置想对应的合适的值。

你可以像下面这样使用Facorial:

 int main()
{
std::cout << Factorial<>::value; // prints 120 std::cout << Factorial<>::value; // prints 3628800 }

如果你认为这比冰激凌更酷,你就已经获得模板元程序员需要的素材。如果模板和特化,递归实例和enum hacks,还有像Factorial<n-1>::value这样的输入使你毛骨悚然,你还是一个“普通的”C++程序员。

3.4 TMP还能够做什么?

当然,Factorial对TMP的功能进行了示范,如同“hello world”程序对任何传统编程语言的功能进行示范一样。为了让你明白为什么TMP是值得了解的,知道它能够做什么很重要,这里有三个例子:

  • 确保因次单位(dimensional unit)的正确性。在科学和工程应用中,把因次单位(例如,质量,距离和时间)正确的拼到一起是很必要的。将表示质量的变量赋值给表示速度的变量是错误的,但是用距离变量除以时间变量然后将结果赋值被速度变量就没有问题。通过使用TMP,确保(在编译期间)程序中的所有因次单元组合的正确性就是可能的,不管计算有多复杂。(这也是使用TMP来侦测早期错误的一个例子。)TMP这种用法的一个有趣的方面是它能够支持分数因次的指数。这需要在编译期间将分数简化,然后编译器才能够确认,例如,单元 time1/2同time4/8是相同的。
  • 优化矩阵操作。Item 21中解释了有一些函数(包括 operator*)必须返回新的对象,Item 44中引入了SquareMatrix类,考虑下面的代码:
    1 typedef SquareMatrix<double, 10000> BigMatrix;
    2 BigMatrix m1, m2, m3, m4, m5; // create matrices and
    3 ... // give them values
    4 BigMatrix result = m1 * m2 * m3 * m4 * m5; // compute their product

    用“普通的”方式来计算result会有四次创建临时matrice对象的调用,每次调用都应用在对operator*调用的返回值上面。这些独立的乘法在矩阵元素上产生了四          次循环。使用TMP的高级模板技术——表达式模板(expression templates),来消除临时对象以及合并循环是有可能的,并且不用修改上面的客户端代码的语法。最   后的程序使用了更少的内存,而且运行速度会有很大的提升。

  • 产生个性化的设计模式实现。像策略模式,观察者模式,访问者模式等等这些设计模式能够以很多方式被实现。使用基于模板的技术被叫做policy-based设计,我们可以创建表示独立设计选择(choice或者叫”policies”)的 模板,这些模板可以以任意的方式进行组合来产生个性化的模式实现。例如,使用这种技术能够创建一些实现智能指针行为策略(policies)的模板,使用它能够产生(在编译期)上百种不同的智能指针类型。这项技术已经超越了编程工艺领域,如设计模式和智能指针,它成为了生殖编程(generative programming)的基础。

4. TMP现状分析

TMP并不是为每个人准备的。因为语法不直观,支持的相关工具也很弱。(像为模板元编程提供的调试器。)作为一个“突然性“的语言它只是最近才被发现的,TMP编程的一些约定正在实验阶段。然而通过将工作从运行时移到编译期所带来的效率提升带给人很深刻的印象,对一些行为表达的能力(很难或者不可能在运行时实现)也是很吸引人的。

对于TMP的支持正在上升期。很可能下个版本的C++就是显示的支持它。TR1中已经支持了(Item 54)。关于这个主题的书籍已经开始出来了,网上的一些关于TMP信息也越来越多。TMP可能永远不会成为主流,但是对于一些程序员来说——尤其是程序库的实现者——几乎必然会成为主要手段。

5. 总结

  • 模板元编程可以将工作从运行时移到编译期,这样可以更早的发现错误,并且提高运行时性能。
  • 基于策略选择(policy choices)的组合TMP能够被用来产生个性化的代码,也能够用来防止为特定类型生成不合适的代码。

读书笔记 effective c++ Item 48 了解模板元编程的更多相关文章

  1. effective c++ Item 48 了解模板元编程

    1. TMP是什么? 模板元编程(template metaprogramming TMP)是实现基于模板的C++程序的过程,它能够在编译期执行.你可以想一想:一个模板元程序是用C++实现的并且可以在 ...

  2. 读书笔记 effective c++ Item 55 让你自己熟悉Boost

    你正在寻找一个高质量的,开源的,与平台和编译器无关的程序库的集合?看一下Boost吧.想加入一个由雄心勃勃的,充满天赋的正致力于最高水平的程序库设计和实现工作的C++程序员们组成的团体么?看一下Boo ...

  3. 读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库

    1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C+ ...

  4. 读书笔记 effective c++ Item 1 将c++视为一个语言联邦

    Item 1 将c++视为一个语言联邦 如今的c++已经是一个多重泛型变成语言.支持过程化,面向对象,函数式,泛型和元编程的组合.这种强大使得c++无可匹敌,却也带来了一些问题.所有“合适的”规则看上 ...

  5. 读书笔记 effective c++ Item 2 尽量使用const,枚举(enums),内联(inlines),不要使用宏定义(define)

    这个条目叫做,尽量使用编译器而不要使用预处理器更好.#define并没有当作语言本身的一部分. 例如下面的例子: #define ASPECT_RATIO 1.653 符号名称永远不会被编译器看到.它 ...

  6. 读书笔记 effective c++ Item 47 使用traits class表示类型信息

    STL主要由为容器,迭代器和算法创建的模板组成,但是也有一些功能模板.其中之一叫做advance.Advance将一个指定的迭代器移动指定的距离: template<typename IterT ...

  7. 读书笔记 effective c++ Item 43 了解如何访问模板化基类中的名字

    1. 问题的引入——派生类不会发现模板基类中的名字 假设我们需要写一个应用,使用它可以为不同的公司发送消息.消息可以以加密或者明文(未加密)的方式被发送.如果在编译阶段我们有足够的信息来确定哪个信息会 ...

  8. 读书笔记 effective c++ Item 44 将与模板参数无关的代码抽离出来

    1. 使用模板可能导致代码膨胀 使用模板是节省时间和避免代码重用的很好的方法.你不需要手动输入20个相同的类名,每个类有15个成员函数,相反,你只需要输入一个类模板,然后让编译器来为你实例化20个特定 ...

  9. 读书笔记 effective c++ Item 45 使用成员函数模板来接受“所有兼容类型”

    智能指针的行为像是指针,但是没有提供加的功能.例如,Item 13中解释了如何使用标准auto_ptr和tr1::shared_ptr指针在正确的时间自动删除堆上的资源.STL容器中的迭代器基本上都是 ...

随机推荐

  1. BZOJ 3210: 花神的浇花集会

    3210: 花神的浇花集会 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 577  Solved: 299[Submit][Status][Discus ...

  2. 【bzoj2034】 2009国家集训队—最大收益

    http://www.lydsy.com/JudgeOnline/problem.php?id=2034 (题目链接) 题意 n个任务,每个任务只需要一个时刻就可以完成,完成后获得${W_i}$的收益 ...

  3. 解题:Poetize6 IncDec Sequence

    题面 差分原数列得到差分数组$dif$,这样对于$dif[2]->dif[n]$会多出来两个“空位置”$1$和$n+1$.然后区间加减就变成了使一个位置$+1$,另一个位置$-1$(可以对“空位 ...

  4. 【2018北京集训(六)】Lcm

    Portal --> 出错啦qwq(好吧其实是没有) Description 给定两个正整数\(n,k\),选择一些互不相同的正整数,满足这些数的最小公倍数恰好为\(n\),并且这些数的和为\( ...

  5. vmware中centos7设置静态IP

    1.vmware—>Edit—>Virtual Network Editor,选中vmnet8-Nat设置,查看网关IP 2.在centos中设置: vi /etc/sysconfig/n ...

  6. poi excel导入纯数字单元格显示科学计数法的处理

    POI读取Excel文件时,对纯数字单元格的处理   用POI读取Excel文件的时候,可能会遇到这样的问题:Excel文件中某一单元格中的数据为数字,例如12345678910123. 正常读取的话 ...

  7. fcntl文件锁操作

    文件锁经常应用于两个方面:1.一是锁定文件中的临界数据,比如并发投票时文件记录的投票数2.二是利用具有互斥性质的写锁,实现进程的并发控制. /*使用文件锁*/<F5>#include &l ...

  8. 287find-the-duplicate-number

    某视面试官问了一道这样的题,1到N(N为正整数)共N个正整数,其中有一个数重复一次覆盖了另外一个数,比如:9,3,7,5,1,8,2,4,5,那么其中5重复一次,相当于覆盖了6,那么,请找出这个重复的 ...

  9. MongoDB用户授权和管理

    转载于https://blog.csdn.net/yu757371316/article/details/55210536 1.mongodb安装好后第一次进入是不需要密码的,也没有任何用户,通过sh ...

  10. array_unshift() 函数用于向数组插入新元素。新数组的值将被插入到数组的开头。

    <?php $a=array("a"=>"red","b"=>"green"); array_unsh ...