C++箴言:审慎利用模板元编程
副标题#e#
template metaprogramming (TMP)(模板元编程)是写 template-based(基于模板)的运行于编译期间的 C++ 措施的进程。思量一下:一个 template metaprogram(模板元措施)是用 C++ 写的运行于 C++ 编译器中的措施。当一个 TMP 措施运行完成,它的输出——从 templates(模板)实例化出的 C++ 源代码片段——随后被正常编译。
假如你仅把它看作离奇的特性而没有冲动你,那你就不会对它有足够的深入的思考。
C++ 并不是为 template metaprogramming(模板元编程)设计的,可是自从 TMP 在 1990 年月早期被发明以来,它已被证明很是有用,使 TMP 变容易的扩展很大概会被插手到语言和它的尺度库之中。是的,TMP 是被发明,而不是被发现。TMP 所基于的特性在 templates(模板)被插手 C++ 的时候就已经被引进了。所需要的全部就是有人留意到它们可以或许以一种精良的并且意想不到的方法被利用。
TMP 有两个强大的气力。首先,它使得用其它要领很难或不行能的一些工作变得容易。第二,因为 template metaprograms(模板元措施)在 C++ 编译期间执行,它们能将事情从运行时转移到编译时。一个功效就是凡是在运行时才气被察觉的错误可以或许在编译期间被发明。另一个功效是 C++ 措施使得 TMP 的利用在以下每一个方面都能更有效率:更小的可执行代码,更短的运行时间,更少的内存需求。(然而,将事情从运行时转移到编译时的一个功效就是编译进程变得更长。利用 TMP 的措施大概比它们的 non-TMP 对等物占用长得多的编译时间。)
思量STL的advance伪代码。(在《C++箴言:为范例信息利用特征类》中。你此刻大概需要读该文,因为在本文中,我假设你已经熟悉了该文的内容。),我突出暗示代码中的伪代码部门:
#p#副标题#e#
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 >= 0) { 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 >= 0) { while (d--) ++iter; } // use iterative calls to
else { while (d++) --iter; } // ++ or -- for other
} // iterator categories
}
《C++箴言:为范例信息利用特征类》中指出这个 typeid-based(基于 typeid)的要领比利用 traits 的要领效率低,因为这个要领,(1)范例检测产生在运行时而不是编译期,(2)用来做运行时范例检测的代码必需呈此刻可执行代码中。实际上,这个例子展示了 TMP 如何能比一个“通例”C++ 措施更高效,因为 traits 要领是 TMP。记着,traits 答允编译时在范例上的 if…else 计较。
我先前谈及一些工作在 TMP 中比在“通例”C++ 中更简朴,而 advance 提供了这方面的一个例子。Item 47 提到 advance 的 typeid-based(基于 typeid)的实现大概会导致编译问题,而这就是一个发生问题的例子:
std::list<int>::iterator iter;
...
advance(iter, 10); // move iter 10 elements forward;
// won't compile with above impl.
思量 advance 为上面这个挪用生成的版本。用 iter 和 10 的范例代替 template parameters(模板参数)IterT 和 DistT 之后,我们获得这个:
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!
}
else {
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}
问题在突出显示的行,利用了 += 的那行。在当前环境下,我们试图在一个 list<int>::iterator 上利用 +=,可是 list<int>::iterator 是一个 bidirectional iterator(双向迭代器)(拜见《C++箴言:为范例信息利用特征类》),所以它不支持 +=。只有 random access iterators(随时机见迭代器)才支持 +=。此时,我们知道我们永远也不会试图执行谁人 += 行,因为谁人 typeid 检测对付 list<int>::iterators 永远不创立,可是编译器被责成确保所有源代码是正确的,纵然它不被执行,而当 iter 不是一个 random access iterator(随时机见迭代器)时 "iter += d" 是不正确的。traits-based(基于 traits)的 TMP 办理方案与此比拟,哪里针对差异范例的代码被疏散到单独的函数中,个中每一个都只利用了可用于它所针对的范例的操纵。
#p#分页标题#e#
TMP 已经被证明是 Turing-complete(图灵完备)的,这意味着它强大得足以计较任何对象。利用 TMP,你可以声明变量,执行轮回,编写和挪用函数,等等。可是这些布局看起来与其在“通例”C++ 中的样子很是差异。譬喻,《C++箴言:为范例信息利用特征类》展示了 if…else 条件在 TMP 中是如何通过 templates(模板)和 template specializations(模板特化)被表达的。但那是 assembly-level(汇编条理)的 TMP。针对 TMP 的库提供了一种更高条理的语法,固然还不至于让你把它误认为是“通例”C++。
为了一窥其它对象在 TMP 中如何事情,让我们来看看 loops(轮回)。TMP 中没有真正的 looping construct(轮回布局),因此 loops(轮回)的结果是通过 recursion(递归)完成的。(假如你对 recursion(递归)感想不舒服,在你果敢进入 TMP 之前必然要办理它。TMP 很洪流平上是一个 functional language(函数性语言),而 recursion(递归)之于 functional language(函数性语言)就像电视之于美国风行文化:是密不行分的。)然而,甚至 recursion(递归)都不是通例样式的,因为 TMP loops 不涉及 recursive function calls(递归函数挪用),它们涉及 recursive template instantiations(递归模板实例化)。
TMP 的 "hello world" 措施在编译期间计较一个阶乘。它不是一个很令人欢快的措施,不外,纵然不是 "hello world",也有助于语言入门。TMP 阶乘计较示范了通过 recursive template instantiation(递归模板实例化)实现轮回。它也示范了在 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-1>::value };
};
template<> // special case: the value of
struct Factorial<0> { // Factorial<0> is 1
enum { value = 1 };
};
给出这个 template metaprogram(模板元措施)(实际上只是单独的 template metafunction(模板元函数)Factorial),你可以通过引用 Factorial<n>::value 获得 factorial(n) 的值。
代码的轮回部门呈此刻 template instantiation(模板实例化)Factorial<n> 引用 template instantiation(模板实例化)Factorial<n-1> 的处所。就像所有正确的 recursion(递归)有一个导致递归竣事的非凡环境。这里,它就是 template specialization(模板特化)Factorial<0>。
Factorial template 的每一个 instantiation(实例化)都是一个 struct,而每一个 struct 都利用 enum hack声明白一个名为 value 的 TMP 变量。value 用于持有阶乘计较的当前值。假如 TMP 有一个真正的轮回布局,value 会在每次轮回时更新。因为 TMP 在轮回的位置利用 recursive template instantiation(递归模板实例化),每一个 instantiation(实例化)获得它本身的 value 的拷贝,而每一个拷贝拥有适合于它在“轮回”中所处的位置的值。
你可以像这样利用 Factorial:
int main()
{
std::cout << Factorial<5>::value; // prints 120
std::cout << Factorial<10>::value; // prints 3628800
}
假如你以为这比吃了冰淇淋还凉爽,你就具有了一个 template metaprogrammer(模板元措施员)应有的素质。假如 templates(模板)以及 specializations(特化)以及 recursive instantiations(递归实例化)以及 enum hacks 以及对雷同 Factorial<n-1>::value 这样的范例的需要使你不寒而栗,好吧,你是一个不错的通例 C++ 措施员。
虽然,Factorial 示范的 TMP 的效用约莫就像 "hello world" 示范的任何通例编程语言的效用一样。为了了解为什么 TMP 值得相识,更好地领略它能做什么是很重要的。这里是三个示例:
Ensuring dimensional unit correctness(确保计量单元正确性)。在科学和工程应用中,计量单元(譬喻,质量,间隔,时间,等等)被正确组合是基本。譬喻,将一个代表质量的变量赋值给一个代表速度的变量是一个错误,可是用一个时间变量去除间隔变量并将功效赋给一个速度变量就是正确的。利用 TMP,岂论计较何等巨大,确保(在编译期间)一个措施中所有计量单元组合都是正确的是有大概的。(这是一个如何用 TMP 举办早期错误诊断的例子。)这个 TMP 的利用的一个有趣的方面是可以或许支持分数指数。这需要这个分数在编译期间被简化以便于编译期可以或许确认,譬喻,单元 time1/2 与单元 time4/8 是沟通的。
#p#分页标题#e#
Optimizing matrix operations(优化矩阵操纵)。《C++箴言:必需返回工具时别返回引用》中阐释了一些函数,包罗 operator*,必需返回新的 objects,而《C++箴言:从模板中疏散出参数无关的代码》一文中则引入了 SquareMatrix class,所以思量如下代码:
typedef SquareMatrix<double, 10000> BigMatrix;
BigMatrix m1, m2, m3, m4, m5; // create matrices and
... // give them values
BigMatrix result = m1 * m2 * m3 * m4 * m5; // compute their product
用“通例”要领计较 result 需要四个姑且矩阵的建设,用于每一次挪用 operator* 的功效。另外,独立的乘法发生了一个四次轮回遍历矩阵元素的序列。利用一种与 TMP 相关的被称为 expression templates(表达式模板)的高级模板技能,完全不改变上面的客户代码的语法,而消除姑且工具以及归并轮回是有大概的。最终的软件利用更少的内存并且运行速度戏剧性地更快。
Generating custom design pattern implementations(生成自界说的设计模式实现)。像 Strategy(拜见《C++箴言:思量可选的虚拟函数的替代要领》),Observer,Visitor 等设计模式能用许多要领实现。利用一种被称为 policy-based design(基于 policy 设计)的 TMP-based(基于 TMP)的技能,使得建设代表独立的设计选择的 templates ("policies") 成为大概,这种 templates 能以任意的要领组合以发生带有自界说行为的模式实现。譬喻,这种技能常常用于答允几个实现了 smart pointer behavioral(智能指针行为)的 policies 的 templates 生成(在编译期间)数百个差异的 smart pointer(智能指针)范例。将雷同设计模式和智能指针这样的编程器件的范畴大大地扩展,这项技能是凡是所说的 generative programming(发生式编程)的基本。
TMP 并不适合于每一小我私家。它的语法是不切合直觉的,东西支持也很弱(template metaprograms 的调试器?哈!)作为一个相对晚近才发明的“隶属”语言,TMP programming 的法则仍然带有试验性质。然而,通过将事情从运行时转移到编译时所提供的效率晋升照旧能给人留下深刻的印象,而表达在运行时很难或不行能实现的行为的本领也相当有吸引力。
TMP 的支持水平在不绝晋升。很大概在 C++ 的下一个版本中将对它提供直接的支持,并且 TR1 已经这样做了。关于这一主题的书籍也即将开始出书(今朝,C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond 已经出书——译者注),而 web 上的 TMP 信息也正在保持增长。TMP 也许永远不会成为主流,可是对付某些措施员——出格是库开拓者——它险些一定会成为主料。
Things to Remember
·template metaprogramming(模板元编程)能将事情从运行时转移到编译时,这样就可以或许更早察觉错误并提高运行时机能。
·TMP 能用于在 policy choices 的组合的基本上生成自界说代码,也能用于制止为非凡范例生成不适当的代码。