Lazy loaded image
5. 泛型编程
Words 1516Read Time 4 min
2026-9-4
🎯
泛型编程的核心:先描述算法需要什么能力,再让编译器为满足这些能力的具体类型生成代码。它解决的是“复用代码”和“避免运行时抽象开销”之间的矛盾。

5.1 为什么需要泛型编程

假设要分别求两个 int、两个 double 中的较大值。直接写会产生重复代码:
两段代码的算法完全相同,变化的只有类型。函数模板把“算法”从“具体类型”中分离出来:

编译器实际做了什么

模板本身更像一份“代码生成规则”。使用模板时,编译器根据实参推导类型,并实例化所需版本。
对比项
面向对象动态多态
泛型编程静态多态
绑定时机
运行期
编译期
常见机制
虚函数、基类指针
模板、Concepts
类型要求
继承统一接口
支持算法所需操作
常见开销
可能发生间接调用
通常可直接调用并内联
💡
泛型编程不是“绝对更快”。更准确地说,它让编译器看到具体类型,从而更容易内联和优化;代价是编译时间增加、错误信息复杂,并可能造成代码体积膨胀。

5.2 函数模板:让算法适配多种类型

类型推导与显式指定

当两个实参类型不一致时,单一模板参数可能无法完成推导:
一种直接的改法是允许两个输入类型不同,并让编译器推导返回类型:

为什么常用 const T&

  • &:避免复制大型对象。
  • const:保证函数不会修改输入。
  • 返回引用:避免再次复制,但调用者必须确保被引用对象仍然存活。

5.3 类模板:构造泛型容器

类模板常用于“数据结构相同、元素类型不同”的场景:
这里既有类型参数 T,也有非类型参数 N。数组容量在编译期确定,不需要动态分配。

模板特化:为特定类型改写实现

特化适合处理确实存在差异的类型;如果只是为了修补一个设计混乱的通用模板,继续加特化通常只会把问题藏得更深。

5.4 函数对象与 Lambda:把行为当作参数

函数对象

重载 operator() 后,对象就可以像函数一样调用,并且能够保存状态:

Lambda

Lambda 是编译器帮助生成的匿名函数对象。捕获列表决定它能使用哪些外部变量:

与 STL 算法配合

这一组合体现了泛型编程的典型分工:
算法不需要知道容器存的是哪种具体类型,只需要迭代器和判断函数提供约定的操作。

5.5 进阶工具解决什么问题

工具
解决的问题
典型用途
可变参数模板
参数数量不固定
日志、工厂函数、元组
类型萃取
在编译期查询或转换类型
判断是否为指针、移除引用
Concepts
明确限制模板参数能力
提前给出可读的编译错误
模板元编程
在编译期完成计算或类型变换
类型列表、编译期配置

Concepts:把隐含要求写出来

传统模板只有在实例化失败时,才可能报出一长串难懂的错误。C++20 Concepts 可以直接声明类型必须满足的条件:
此时,模板表达的不再是“任何类型都可以”,而是“任何满足 Addable 要求的类型都可以”。这才是泛型约束的准确含义。

5.6 泛型编程的思想

泛型编程不是简单地把类型名替换成 T,而是三步:
  1. 找出算法中不变的部分,例如遍历、比较、排序。
  1. 把变化的部分抽成类型、数值或行为参数。
  1. 明确参数必须提供的最小能力,例如可比较、可调用、可迭代。
判断是否真正理解模板:不要只问“这里的 T 是什么”,而要问“这段算法对 T 做了哪些操作,因此 T 至少必须具备什么能力”。
泛型编程的价值可以压缩成一句话:用编译期约束表达共性,用具体类型保留性能。
上一篇
C++ String 常用操作
下一篇
嵌入式工程师习题集

Comments
Loading...