C++ —— 使用模板元编程来进行递归运算的优化

模板可以被用做预编译程序,Todd Veldhuizen和David Vandevoorde指出,任何算法都能被模板化,算法的输入参数在编译期提供。只要有好的编译器,中间代码可以完全优化掉。

对斐波拉契数列的优化

斐波拉契数列,老生常谈啦,一开始学递归就学这个东西,通常下面这种方法都是明令禁止的:

unsigned int fib(unsigned int n) {
    if (n == 0 || n == 1)
    {
        return 1 ;
    }
    else
    {
        return fib( n - 1) + fib (n - 2); 
    }
}

原因很简单,它在运行的时候会不停压栈,容易引起栈溢出的情况。

但是有一种办法可以适用模板元编程来进行优化。很多人其实不知道模板可以作为虚拟编译程序,可以快速大量地创建优化代码。

此外,由于算法的输入参数是在编译期提供的,因此不会在runtime的时候进行重复的操作,这样一来可以达到非常高的效率。

那么该如何进行优化以上代码?

template <unsigned int N>
struct FibR 
{ 
    enum 
    { 
        Val = FibR< N-1 >:: Val + FibR::Val 
    }; 
};

template <>
struct FibR <0> 
{ 
    enum 
    { 
        Val = 1 
    };
}; 

template <>
struct FibR <1> 
{ 
    enum 
    { 
        Val = 1 
    };
};
#define fib(n) FibR::Val

这样一来,我们可以通过#define来调用这个模板。

std::cout << fib (4) << std::endl;

需要注意的是,模板函数实际上不是真正的函数——它实际上是一个枚举整数,在编译期递归生成。语句Val = FibR< N-1 >:: Val + FibR::Val虽然不是很常见,但是是完全合法的。

FibR定义为一个Struct,是因为它的数据默认都是public的。而Val采用枚举整数的原因是它可以预先就指定它的Value。

当然,有递归,当然就要有结束条件。在模板中处理基本情况的方法就是使用模板特化(template specialization)。

凡是由template <>标记的,就意味着这是模板特化。那么对于fib(4)来说,编译器是这么玩的:

fib (4)
= FibR< 4 >::Val
= FibR< 3 >::Val + FibR< 2 >::Val
= FibR< 2 >::Val + FibR< 1 >::Val + FibR< 1 >::Val + FibR< 0 >::Val
= FibR< 0 >::Val + FibR< 1 >::Val + FibR< 1 >::Val + FibR< 1 >::Val + FibR< 0>:: Val
= 1 + 1 + 1 + 1 + 1
= 5

注意这是编译器玩的东西,所有的输入都在编译期间确定,因此在最终,编译器生成的代码就是:

std::cout << 5;

这种方法是C++中的一种很有用的方法。有些时候对于某些指数级的运行时间的函数,死都不能降为常数级运行时,可以考虑使用这种编程方式。

这样一来就可以通过增加额外的编译时间来降低程序的执行时间。当然对于游戏来说,执行时间肯定比编译时间重要。

阶乘运算

通常的做法是:

unsigned int fact (n ) 
{ 
    return n <= 1 ? 1 : n * fact( n - 1); 
}

但是如果使用模板元编程,那么代码就是:

template < unsigned int N >
struct FactR
 { 
    enum { 
        Val = N * FactR::Val 
    }; 
};

template <> 
struct FactR < 1 >
{
    enum 
    {
        Val = 1
    };
}; 

#define fact(n) FactR::Val

就和斐波拉契数列一样,编译器会将最终的运算调用进行换算,也就是说降成了常数级的运行时间,这就是使用元编程的好处。

反思

模板元编程当然也存在一些缺点:

  • 编译时间的损失,当然这一点通常不会特别重要。我习惯在代码编译的时候上个厕所喝杯咖啡啥的……
  • 代码可读性有些损失,但是我们可以尽量避免,比如使用宏定义等。

模板元编程虽然很有意思,而且很高效,但是说实话在项目中,这种东西用的真的特别多吗(注:这篇博客写于2015年8月,现在可以回答这个问题了:在游戏中其实对于矩阵乘法等操作都可以用到模板元编程,因此它还是很有必要去掌握的)?我不禁陷入了沉思的大波之中……

已标记关键词 清除标记
相关推荐
C++模板编程领域的经典著作,由资深C++开发工程师撰写,以透彻分析原理为前提,深入讲解了模板编程的基本原理、标准库中算法与容器等模板的实现原理;本书以实践为导向,通过大量的模板向读者展示了如何使用模板进行编程以及如何编写自定义模板。除此之外,本书还总结了各种常用的模板编程技巧、C++11新标准中的模板新特性和新语法,以及C++11中新增的其他语言特性。 全书共16章,分为四部分:第一部分(1~4章)首先介绍了模板编程的基本概念与用法,然后重点讨论了编译器对模板的具体实现方法及其局限,读者可以通过本部分内容理解模板的基本原理并自行实现简单的类模板与函数模板;第二部分(第5~9章)对标准库中的算法与容器的实现原理和用法进行了深入地剖析,读者可以通过本部分内容对标准库中的算法、迭代器与容器之间的关系有深入的理解,从而可以精确调节标准容器的行为以及自行开发适用于标准算法的容器类模板;第三部分(10~13章)讨论了模板编程的高级技巧,如模板编程中“概念”的设计、控制代码量的技术、编译期逻辑的控制以及编程的基本方法等,读者可以通过本部分内容开发更具规模、更加智能的模板库,并利用编程技术实现编译期的逻辑演绎与类型推导;第四部分(14~16章)介绍了C++11新标准中的新增语言特性,以及对模板编程的影响。 封底: C++是一种强类型语言,同时支持面向对象的设计方法。在强类型要求下,如果机械地遵循“万物皆对象”的法则,同一种算法需要在不同的数据类型操作中重复实现,或者是设计一个抽象的类型专门容纳算法。无论哪一种方案,都有“削足适履”之嫌。C++中的模板很好地解决了这一问题!是否理解模板编程的原理以及是否掌握了模板编程的方法是普通C++程序员和高级C++程序员的分水岭。深入实践C++模板编程,不仅将进一步提升你的编程能力,而且会革新你的C++编程思想。 本书是近年来C++领域难得的作品,更是C++模板编程领域难得的佳作之一。它不仅深入剖析了模板编程的基本原理、标准库中算法与容器等模板的实现原理,而且还讲解了模板编程的高级技巧以及C++11新标准对模板的相应更新,更重要的是它从实践地角度讲解了如何进行模板编程和编写自定义的模板,为C++程序员进阶提供了很好的指导。 本书主要内容如下: (1)函数模板的原理、用法、模板参数自动推导,以及函数体的实现方法; (2)外名模板及其原理; (3)类模板及其用法,异质链表、组构造方法,以及类模板的静态成员的处理; (4)各种整数型、指针型及引用型模板参数类型详解; (5)模板的特例,函数模板的重载,特例的写法及匹配规则; (6)标准库中的容器、迭代器和算法的定义、作用以及它们之间的关系; (7)标准库中的序列型容器、关联型容器、散列表容器、C++11新增的容器等各种类型的容器的实现原理; (8)各种分配器和迭代器的实现原理及其编写方法; (9)标准库中常用算法的原理分析及其应用实践; (10)各种常用的模板编程技巧,以及编程技术; (11)C++11新标准的模板新特性和新语法,以及C++11新标准中新增的语言特性解读。 前: C++语言支持多种编程范式,其中,面向过程与面向对象最为人熟悉,相关书籍也不胜枚举,唯独模板编程少有书籍谈及,即使谈及,也多是泛泛介绍,一带而过。其实模板C++中不容忽视的一个强有力的特性。模板将类型做为一种“变量”抽象出来,使得代码的适用范围进一步提高。又因为“类型”之抽象,更促使开发者在分析与解决问题时,不仅满足于个案分析,更要从不同类型的问题中提取出共性并加以实现。了解进而熟练使用模板的过程,是对开发者编程思路的又一次历练与升华。可惜专门介绍C++模板编程的图书十分稀少,使之成为一条少有人走的路。 而本书恰好弥补了这个空白。在本书中,您可以领略到模板的精密原理、容器与算法的渊源、泛型编程的奇思妙想,以及模板C++11中的豹变。同时,这也是一本有趣的计算机图书,作者用生动的语言、精巧的构思以及详尽的示例,定会使读者在学习模板编程的过程中体会到发现新风景的愉悦。对于C++语言进阶读者,强烈推荐本书。 —— 广州三星通信研究院 首席研究员 软件研发总监 戴天荣 温宇杰 资深C++软件工程师,有多年C++开发经验,对C++模板编程编程有非常深入的研究,实践经验十分丰富。擅长组合优化算法及大规模集成电路辅助设计算法,对FPGA体系结构以及Verilog和VHDL等硬件描述语言也有非常深入的研究,曾主持并参与开发了一套完整设计流程的FPGA平台编译软件。 第一部分 模板基础 1 第1章 Hello模板 2 1.1 为什么需要模板 2 1.2 初识函数模板 3 1.2.1 函数模板的实现 3 1.2.2 如何使用函数模板 4 1.2.3 模板参数自动推导 5 1.2.4 模板参数默认值 7 1.2.5 模板函数的静态变量 8 1.3 如何处理函数模板中的函数体 8 1.3.1 HPP文件还是CPP文件 9 1.3.2 链接器如何识别重复模板实例 10 1.4 尴尬的Export Template 13 1.4.1 什么是外名模板 13 1.4.2 C++编译器对外名模板的处理 14 1.5 本章小结 15 第2章 类亦模板 16 2.1 类型无关的数据结构 16 2.2 实践——栈类模板 17 2.2.1 栈类模板实例 17 2.2.2 栈类模板衍生子类模板实例 20 2.3 突破——异质链表 21 2.4 构造组 23 2.4.1 通过嵌套实现组 23 2.4.2 用类实现组 24 2.5 类模板的用法 25 2.5.1 成员函数模板 25 2.5.2 友函数模板 26 2.6 类模板的静态成员 27 2.7 本章小结 30 第3章 模板参数类型详解 31 3.1 整数模板参数 31 3.2 函数指针模板参数 32 3.3 指针及引用模板参数 34 3.4 成员函数指针模板参数 35 3.5 模板模板参数 37 3.6 本章小结 39 第4章 凡事总有“特例” 40 4.1 从vector说起 40 4.2 特例的多种写法 44 4.3 特例匹配规则 46 4.4 函数模板的特例与重载 47 4.4.1 分辨重载 50 4.4.2 编译期的条件判断逻辑 52 4.5 本章小结 54 第二部分 标准库中的模板 55 第5章 容器、迭代器与算法 56 5.1 容器的定义 56 5.2 容器的实现 56 5.2.1 Java的实现方法 57 5.2.2 C++的实现方法 60 5.3 容器与迭代器 62 5.3.1 链表容器与迭代器 64 5.3.2 集合容器与迭代器 67 5.4 迭代器与算法 71 5.4.1 求容器中素之和 71 5.4.2 实例:微型算法库 73 5.5 容器与迭代器的分类 75 5.6 容器与算法的关系 76 5.7 迭代器的陷阱 76 5.8 本章小结 77 第6章 标准库中的容器 79 6.1 容器的分类及基本要求 79 6.2 序列型容器 81 6.2.1 变长数组vector 82 6.2.2 双向链表list 84 6.2.3 双端序列deque 85 6.3 容器转换器 87 6.3.1 栈stack与队列queue 87 6.3.2 优先队列priority_queue 88 6.4 关联型容器 89 6.4.1 基本数据结构 89 6.4.2 内嵌类型定义 92 6.4.3 构造关联型容器 92 6.4.4 插入数据 93 6.4.5 数据的删除、查找与访问 96 6.4.6 整数值专用集合bitset 98 6.5 散列表容器 99 6.5.1 基本数据结构 99 6.5.2 散列函数 100 6.5.3 桶 101 6.6 其他C++11新容器 104 6.6.1 定长数组array 104 6.6.2 单向链表forward_list 105 6.7 本章小结 106 第7章 隐形的助手——分配器 107 7.1 分配器的基本要求 107 7.2 交换容器内容时的特殊处理 110 7.3 有态分配器与无态分配器 112 7.4 实践:池分配器 114 7.4.1 池分配器模板类的设计 115 7.4.2 对象池的实现 116 7.4.3 定位构造 121 7.4.4 池分配器的实现 122 7.4.5 测试池分配器 127 7.4.6 实际运行 129 7.5 本章小结 131 第8章 标准库中的迭代器 132 8.1 迭代器分类 132 8.1.1 输入迭代器 132 8.1.2 前向迭代器 133 8.1.3 双向迭代器与跳转迭代器 135 8.1.4 输出迭代器 136 8.2 迭代器属性类模板 137 8.3 迭代器转换器 139 8.3.1 反转迭代器 139 8.3.2 插入迭代器 141 8.4 流迭代器 142 8.5 本章小结 144 第9章 标准库中的算法 145 9.1 算法的共同特征 145 9.2 标准库中的常用算法 145 9.2.1 foreach的三种写法 146 9.2.2 搜索 147 9.2.3 计数与比较 149 9.2.4 复制、交换、替换与删除 149 9.2.5 排序 151 9.2.6 二分搜索 151 9.2.7 集合运算 152 9.2.8 二叉堆操作 154 9.2.9 其他算法 154 9.3 预设函数对象 155 9.3.1 函数对象基类 155 9.3.2 运算函数对象 156 9.3.3 参数绑定 157 9.4 实践:矩阵操作中如何消除循环语句 165 9.4.1 跨跃迭代器 165 9.4.2 矩阵类模板 167 9.4.3 累计迭代器 169 9.4.4 矩阵乘法 170 9.4.5 矩阵LU分解 171 9.4.6 组合迭代器 172 9.4.7 没有循环语句的矩阵乘法 177 9.5 本章小结 178 第三部分 模板编程高级技巧 179 第10章 专用名词——概念 180 10.1 模板的先天不足 180 10.2 “概念”的提案及ConceptGCC编译器 181 10.3 概念语法 183 10.3.1 定义概念 183 10.3.2 用概念约束模板参数 184 10.3.3 概念映射 184 10.4 概念模拟库 186 10.4.1 概念检查宏 187 10.4.2 自定义概念检查 189 10.4.3 概念典型 190 10.5 本章小结 191 第11章 代码膨胀 192 11.1 源代码的增加 192 11.1.1 代理类的困境 192 11.1.2 D语言的方法 195 11.2 目标代码的增加 196 11.2.1 目标代码膨胀的成因 196 11.2.2 目标代码膨胀实例 197 11.2.3 改进代码 198 11.2.4 测试改进效果 206 11.3 本章小结 208 第12章 常用模板编程技巧 209 12.1 标签与特性 209 12.1.1 特性类模板numeric_limits 209 12.1.2 实例:矩阵与向量乘法 211 12.2 编译期多态 213 12.2.1 全覆盖的函数模板 213 12.2.2 虚函数的启发 213 12.2.3 虚基类模板 214 12.3 策略 217 12.3.1 策略的产生:再说vector的不足 217 12.3.2 为vector添加存储策略 218 12.4 伪变长参数模板 223 12.4.1 hetero_node的启发 224 12.4.2 编译期递归 225 12.4.3 访问组中的数据 227 12.5 本章小结 230 第13章 编程 231 13.1 C++中的编程 231 13.2 函数 231 13.2.1 函数的实现 231 13.2.2 函数的调用 233 13.3 容器与算法 235 13.3.1 容器的实现 235 13.3.2 实例:容纳5种类型的容器 236 13.4 类型过滤 240 13.4.1 类型过滤函数的实现 240 13.4.2 实例:应用容器与算法 242 13.5 本章小结 244 第四部分 模板C++11 245 第14章 右值引用 246 14.1 右值引用的产生 246 14.1.1 函数的匿名返回值 246 14.1.2 返回值优化 249 14.2 右值引用基本概念 251 14.2.1 左值与非左值 251 14.2.2 右值与右值引用 252 14.2.3 移动构造与移动赋值 252 14.2.4 狭义与广义的右值 253 14.2.5 左值强制转义成右值引用 254 14.2.6 右值引用变量是左值 255 14.3 引用声明符消去规则 256 14.3.1 完美转发 256 14.3.2 实例:智能的min函数 260 14.4 移动与异常 263 14.4.1 迁移数据的风险 263 14.4.2 关键字noexcept 265 14.4.3 转义函数模板 267 14.4.4 移动的效率问题 268 14.5 本章小结 269 第15章 模板新语法 270 15.1 变长参数模板 270 15.1.1 参数包 271 15.1.2 参数包的内容 272 15.1.3 参数包的展开模式 273 15.1.4 遍历参数包的内容 274 15.1.5 轻松实现组 275 15.2 扩展的类型推导机制 276 15.2.1 自动类型变量 277 15.2.2 提取表达式结果类型 278 15.2.3 函数后置返回类型 280 15.3 其他模板新特性 281 15.3.1 外部模板实例 281 15.3.2 模板别名 282 15.3.3 连续的右尖括号 282 15.4 本章小结 283 第16章 C++11新特性集锦 284 16.1 λ表达式 284 16.1.1 λ表达式语法 284 16.1.2 变量捕获 285 16.2 初值列表新用法 290 16.2.1 构造变量 290 16.2.2 初值封装类模板 291 16.3 标准容器与算法的变化 292 16.3.1 对应右值引用 292 16.3.2 对应变长参数模板 293 16.3.3 对应初值列表 294 16.4 标准组类模板 294 16.5 智能指针 296 16.5.1 独占指针unique_ptr 297 16.5.2 共享指针shared_ptr与weak_ptr 298 16.6 基于范围的for循环 299 16.7 拾遗 300 16.8 本章小结 301
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页