条款48:认识模板元编程
1.1 模板元编程
- 模板元编程(Template metadata programming, TMP)是编写在编译期间执行的基于模板的C++程序的过程。TMP是被发现的,而不是发明的。
- 特点是:更小的可执行文件,更短的运行时间,更少的内存需求,但需要更长的编译时间。
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;
}
else {//对于其他迭代器类别,使用对++或——的迭代调用
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}
//基于typeid的方法比使用traits的方法效率低,因为
//(1)类型测试发生在运行时,而不是在编译期间;
//(2)进行运行时类型测试的代码必须存在于可执行文件中。
1.2 提出问题
advance基于typeid的实现可能会导致编译问题:
std::list<int>::iterator iter;
...
advance(iter, 10); // 基于上面的实现,无法编译通过
考虑上述调用将生成的advance版本:
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; // 错误!
} else {
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}
相比之下,基于traits的TMP解决方案将不同类型的代码拆分为单独的函数,每个函数只使用适用于它所编写的类型的操作。
1.3 举个栗子
TMP已经被证明是图灵完备的,这意味着它足够强大,可以计算任何东西。
template<unsigned n> // 一般情况:
struct Factorial { // Factorial<n>的值是Factorial<n-1>的值的n倍
enum { value = n * Factorial<n - 1>::value };
};
//TMP使用递归模板实例化来代替循环
template<> // 特殊情况:
struct Factorial<0> { // Factorial<0>的值为1
enum { value = 1 };
};
int main()
{
std::cout << Factorial<5>::value; // 打印 120
std::cout << Factorial<10>::value; // 打印 3628800
}
1.4 使用场景
TMP的使用场景,下面提供了三个:
- 确保尺寸单位的正确性。
- 优化矩阵运算,请看下面的代码:
typedef SquareMatrix<double, 10000> BigMatrix;
BigMatrix m1, m2, m3, m4, m5; // 创建矩阵
... // 给它们赋值
BigMatrix result = m1 * m2 * m3 * m4 * m5; // 计算
- 生成自定义设计模式实现。
使用与TMP相关的一种高级模板技术——表达式模板(expression templates),可以消除临时变量并合并循环,不需要改变客户代码的语法。由此产生的软件使用的内存更少,运行速度更快。
1.5 总结
- 模板元编程可以将工作从运行时转移到编译时,从而支持更早的错误检测和更高的运行时性能。
- TMP可用于根据策略选择的组合生成自定义代码,它还可用于避免生成不适合特定类型的代码。