C++ 模板与泛型编程
更新时间:2026-08-25。本文是
languages/cpp/主题第 ④ 篇。模板是 C++"零成本抽象"的引擎——代码在编译期按类型展开,运行时零开销,这是 C++ 与 Java/C# 泛型最本质的区别。
本文要回答的问题
- 函数模板和类模板怎么写?实例化发生在什么时候?
- 什么是特化?全特化与偏特化怎么用?
- C++20 的 concepts 如何让模板错误可读?
编译期展开:一次模板,N 份代码

一、函数模板与类模板
1.1 函数模板
cpp
template <typename T>
T max_of(T a, T b) {
return a > b ? a : b;
}
int m1 = max_of(3, 7); // 实例化 int 版本
double m2 = max_of(3.14, 2.71); // 实例化 double 版本编译期生成两份代码(int 和 double 各一份)——每用一次类型就展开一次,这就是"模板 = 编译器代写代码"。
1.2 类模板
cpp
template <typename T>
class Stack {
std::vector<T> data_;
public:
void push(const T &v) { data_.push_back(v); }
T pop() { auto v = data_.back(); data_.pop_back(); return v; }
};
Stack<int> si; // Stack<int>
Stack<std::string> ss; // Stack<std::string>:互不相同的类型二、模板特化
2.1 全特化:为特定类型定制
cpp
template <typename T>
T max_of(T a, T b) { return a > b ? a : b; }
// 全特化:const char* 比较内容而非指针
template <>
const char *max_of<const char *>(const char *a, const char *b) {
return std::strcmp(a, b) > 0 ? a : b;
}2.2 偏特化:只针对部分参数(仅类模板支持)
cpp
// 指针版本的 Stack
template <typename T>
class Stack<T *> {
// 专门处理指针:自动 delete 等
};| 特化类型 | 语法 | 适用 |
|---|---|---|
| 全特化 | template <> ... max_of<const char*> | 函数/类模板 |
| 偏特化 | template <typename T> class Stack<T*> | 仅类模板 |
| 偏特化(参数) | template <typename T> class Stack<std::vector<T>> | 仅类模板 |
三、变参模板与折叠
3.1 变参模板
cpp
template <typename... Args>
void print_all(Args... args) { // 参数包
(std::cout << ... << args); // C++17 折叠表达式
}
print_all(1, "hello", 3.14);Args...是模板参数包,args...是函数参数包。- 递归展开(老方法)或 折叠表达式(
...直接展开,C++17)。
3.2 if constexpr:编译期分支
cpp
template <typename T>
void process(T v) {
if constexpr (std::is_integral_v<T>) {
// 仅整型时编译此分支
} else {
// 其他类型时编译此分支
}
}if constexpr 在编译期丢弃未选中分支——不做运行期判断,还能编译"本不该实例化"的代码(如对整型调用字符串专属 API)。
四、SFINAE 与 concepts
4.1 问题:模板错误的可读性
C++20 之前,模板匹配失败的错误信息是几百行晦涩编译错误。SFINAE(替换失败不是错误)是 C++11~17 时代的约束手段,但语法晦涩:
cpp
template <typename T>
auto length(const T &c) -> decltype(c.size()) { // 只有有 .size() 才匹配
return c.size();
}4.2 C++20 concepts:把约束变成语言特性
cpp
template <typename T>
concept HasSize = requires(T t) { t.size(); };
template <HasSize T> // 约束模板参数
size_t length(const T &c) { return c.size(); }
length(std::vector<int>{1,2,3}); // OK:有 size()
// length(42); // 编译错误:明确提示 int 不满足 HasSize| 约束方式 | 时代 | 可读性 | 运行时开销 |
|---|---|---|---|
| 无约束模板 | C++98 | 报错难懂 | 0 |
| SFINAE | C++11~17 | 晦涩 | 0 |
| concepts | C++20 | 报错清晰 | 0 |
五、模板与本站性能主线衔接
- 零成本抽象:模板在编译期展开,
-O2内联后无函数调用开销——与虚函数(运行期多态,有间接跳转)对比,见 L4 编译器优化。 - 编译期计算:
constexpr/if constexpr把运行期工作搬到编译期,减少运行热点。 - 代码膨胀:模板过度实例化会增大 .text,影响指令缓存(I-cache)——用 perf 观察。详见 L3 缓存。
- 崩溃/错误:模板错误信息复杂,用
-fdiagnostics-color与 concepts 提升可读性。
一句话总结
模板 = 编译期按类型展开的零成本抽象(函数/类模板 + 全特化/偏特化 + 变参折叠 + concepts 约束);与虚函数相比,模板把多态决策从运行期提前到编译期,换来性能、换来可读的错误提示。
上一节:移动语义与右值引用 上一篇:C++ 现代特性总纲