c++ 泛型编程的核心是模板机制,它允许你编写与具体数据类型无关的通用代码,编译时再根据实际类型生成具体函数或类 。其主要目的是代码复用和编译时类型安全,避免为每种类型重复编写相同逻辑 。
泛型编程的核心是什么
- 模板是基础:c++ 中泛型编程主要通过模板实现,包括函数模板和类模板 。
- 编译时生成:模板本身不是可执行代码,而是一个“蓝图”。编译器在编译时根据调用时传入的类型,实例化出具体的函数或类版本 。
- 静态多态:与虚函数的运行时多态不同,模板实现的是编译时的静态多态,类型在编译期确定,通常性能更高 。
函数模板怎么写
- 定义语法:使用
template关键字声明模板参数,常用typename t或class t表示任意类型 。- 示例:
template<typename t> void swap(t& a, t& b) { t temp = a; a = b; b = temp; }
- 示例:
- 调用方式:
- 自动推导:直接传入参数,编译器根据实参自动推断类型,如
swap(x, y)。 - 显式指定:在函数名后用尖括号指定类型,如
swap<int>(a, b)。
- 自动推导:直接传入参数,编译器根据实参自动推断类型,如
- 匹配原则:如果存在同名的非模板函数,编译器优先调用非模板函数;若模板实例化后匹配度更高,则调用模板 。
类模板怎么用
- 定义通用类:类模板用于定义与类型无关的类,如通用栈、队列等 。
- 示例:
template<class t> class stack { t* _array; ... };
- 示例:
- 实例化对象:创建对象时必须在类名后指定具体类型,如
stack<int> st;。 - 成员函数:类模板内部的成员函数若在类外定义,也需加上
template<class t>声明 。
1.模板参数的类型
模板参数分为三大类:
| 类别 | 语法 | 说明 |
|---|---|---|
| 类型参数 | typename t / class t | 代表一个类型 |
| 非类型参数 | int n, auto v (c++17) | 编译期常量值,可为整型、枚举、指针、引用、成员指针等 |
| 模板模板参数 | template<class> class container | 参数本身是一个类模板 |
// 类型参数 + 非类型参数 + 模板模板参数
template<
typename t, // 类型参数
size_t n, // 非类型参数
template<typename> class alloc // 模板模板参数
>
class array {
t data[n];
alloc<t> allocator;
};2.函数模板
2.1.基本形式
template<typename t>
t add(t a, t b) {
return a + b;
}
// 编译器自动推导:add(1, 2) -> t=int
// 显式指定:add<double>(1, 2)2.2.函数模板重载
函数模板之间、模板与非模板之间可以重载,匹配优先级为:
- 普通非模板函数(精确匹配)
- 特化版本(如果存在)
- 更"特化"的模板(部分排序规则)
- 更泛化的模板
// 通用版本
template<typename t>
void foo(t x) { cout << "generic\n"; }
// 指针版本(更特化)
template<typename t>
void foo(t* x) { cout << "pointer\n"; }
// 非模板函数
void foo(int x) { cout << "non-template int\n"; }
int a = 1;
foo(a); // 调用非模板函数
foo(&a); // 调用指针版本
foo(3.14); // 调用通用版本2.3.函数模板的默认参数
template<typename t = int, typename u = double>
void print(t a, u b) {
cout << a << ", " << b << endl;
}
print<>(1, 1); // t=int, u=double
print(1, 2); // t=int, u=double
print(1, "hello"); // t=int, u=const char*注意:函数模板不支持偏特化,只能用重载来实现类似效果。
3.类模板
3.1.基本形式
template<typename t, size_t n = 10>
class stack {
t data[n];
size_t top = 0;
public:
void push(const t& val) { data[top++] = val; }
t& pop() { return data[--top]; }
};3.2.全特化(full specialization)
对特定类型组合提供完全独立的实现。
template<typename t, typename u>
class pair {
t first;
u second;
};
// 全特化:当 t=void, u=void 时
template<>
class pair<void, void> {
// 完全不同的实现
};3.3.偏特化(partial specialization)
对模板参数的部分约束提供特化,是类模板独有特性。
template<typename t, typename u>
class mycontainer { /* 通用实现 */ };
// 偏特化1:第二个参数固定为 int
template<typename t>
class mycontainer<t, int> { /* ... */ };
// 偏特化2:两个参数相同
template<typename t>
class mycontainer<t, t> { /* ... */ };
// 偏特化3:t 是指针类型
template<typename t, typename u>
class mycontainer<t*, u> { /* ... */ };
// 偏特化4:t 是引用类型
template<typename t, typename u>
class mycontainer<t&, u> { /* ... */ };偏特化匹配规则:编译器选择最特化(most specialized)的版本。例如:
mycontainer<int*, int> obj1; // 匹配 t*, u(指针偏特化) mycontainer<int, int> obj2; // 匹配 t, t(相同类型偏特化) mycontainer<int, double>obj3; // 匹配通用版本
4.sfinae 与约束(c++11/17/20)
在模板元编程中,sfinae(substitution failure is not an error)用于控制模板参与重载决议。
sfinae(substitution failure is not an error)是 c++ 模板系统的核心规则:
当编译器尝试用具体类型替换模板参数时,如果推导失败,不会产生编译错误,而是将该模板从重载候选集中移除。
// c++11/14: 使用 enable_if
template<typename t>
typename std::enable_if<std::is_integral<t>::value, t>::type
foo(t x) { return x; }
// c++20: 使用 concept(更简洁)
template<typename t>
concept integral = std::is_integral_v<t>;
template<integral t>
t foo(t x) { return x; }4.1.最简示例:理解"推导失败不是错误"
// 通用版本:接受任意类型
template<typename t>
void foo(t x) {
cout << "generic: " << x << endl;
}
// 指针版本:t 必须能解引用出 int 成员
template<typename t>
void foo(t x, typename t::iterator* = nullptr) {
cout << "has iterator\n";
}
foo(42); // 调用通用版本
// 尝试匹配指针版本时:
// t=int, int::iterator 不存在 → 推导失败
// 但这不是编译错误!只是移除此候选4.2.sfinae 发生的时机
模板定义 → 类型替换(substitution) → 推导失败?
↓ ↓ ↓
编译期 编译期(重载决议) 是 → 静默丢弃
否 → 继续编译关键点:sfinae 只在替换阶段生效,函数体内部的错误不会被 sfinae 捕获。
4.3.enable_if:sfinae 的"开关"
std::enable_if 是 <type_traits> 中的工具,本质是条件类型开关。
4.3.1.原理剖析
// enable_if 的实现(简化版)
template<bool b, typename t = void>
struct enable_if {};
// 偏特化:当 b=true 时生效
template<typename t>
struct enable_if<true, t> { using type = t; };
// 使用
enable_if<true, int>::type // ok, type = int
enable_if<false, int>::type // 错误!没有 type 成员4.3.2.两种使用位置
- 位置一:返回值(最常用)
template<typename t>
typename std::enable_if<std::is_integral<t>::value, t>::type
foo(t x) { return x; }
// 当 t=double 时:
// enable_if<false, double>::type → 不存在
// 推导失败 → sfinae → 此模板被移除- 位置二:模板参数列表
template<
typename t,
typename = typename std::enable_if<std::is_integral<t>::value>::type
>
t foo(t x) { return x; }
// 优点:不占用返回值位置,更通用
// 缺点:默认模板参数不参与函数签名匹配- 位置三:函数参数列表(c++11 常用技巧)
template<typename t>
t foo(t x,
typename std::enable_if<std::is_integral<t>::value, int>::type = 0
) { return x; }
4.3.3.多条件组合
// 条件:t 是整数 且 有符号
template<typename t>
typename std::enable_if<
std::is_integral<t>::value && std::is_signed<t>::value,
t
>::type
process(t x) { return -x; }
// c++14 起可用 _v 简写
template<typename t>
std::enable_if_t<std::is_integral_v<t> && std::is_signed_v<t>, t>
process(t x) { return -x; }4.4.sfinae 实战:类型特征检测
4.4.1.检测类型是否有某个成员
5.stl 中模板的常见使用模式
5.1.容器(containers)
std::vector<int> v; // 类型参数 std::array<int, 100> a; // 类型 + 非类型参数 std::map<std::string, int> m; // 多类型参数 std::vector<int, myalloc<int>> v2; // 模板模板参数(分配器)
5.2.迭代器(iterators)
迭代器是模板参数化的指针抽象,算法通过模板参数接受任意迭代器类型:
template<typename inputit, typename t>
inputit find(inputit first, inputit last, const t& value) {
for (; first != last; ++first)
if (*first == value) return first;
return last;
}
5.3.算法(algorithms)
stl 算法大量使用函数模板,典型模式:
// 1. 接受迭代器范围
std::sort(v.begin(), v.end());
// 2. 接受自定义比较器(函数对象/仿函数)
std::sort(v.begin(), v.end(), std::greater<int>());
// 3. 接受一元/二元操作
std::transform(in.begin(), in.end(), out.begin(),
[](int x) { return x * 2; });5.4.适配器(adapters)
通过模板组合和默认参数实现功能扩展:
// stack 默认底层容器是 deque
template<typename t, typename container = std::deque<t>>
class stack {
container c;
public:
void push(const t& x) { c.push_back(x); }
// ...
};
std::stack<int> s1; // 底层为 deque
std::stack<int, vector<int>> s2; // 底层为 vector5.5.仿函数 / 函数对象(functors)
重载 operator() 的类模板,用于向算法传递策略:
template<typename t>
struct plus {
t operator()(const t& a, const t& b) const {
return a + b;
}
};
std::plus<int> adder;
int sum = adder(3, 4); // 75.6.策略模式(policy-based design)
通过模板参数注入行为策略,典型如 std::shared_ptr 的删除器:
template<typename t, typename deleter = std::default_delete<t>>
class unique_ptr {
t* ptr;
deleter del;
public:
~unique_ptr() { del(ptr); }
};5.7.类型萃取(type traits)
编译期类型信息查询与转换,是模板元编程的基础:
// 查询 static_assert(std::is_pointer_v<int*>); // true static_assert(std::is_same_v<int, int32_t>); // true (通常) // 转换 using t = std::remove_reference_t<int&>; // t = int using u = std::add_pointer_t<int>; // u = int*
6.总结对比表
| 特性 | 函数模板 | 类模板 |
|---|---|---|
| 自动类型推导 | ✅ 支持 | ❌ 需显式指定(除 ctad c++17) |
| 全特化 | ✅ 支持 | ✅ 支持 |
| 偏特化 | ❌ 不支持(用重载替代) | ✅ 支持 |
| 默认模板参数 | ✅ 支持(c++11 起尾部可省) | ✅ 支持 |
| 非类型模板参数 | ✅ 支持 | ✅ 支持 |
| 模板模板参数 | ✅ 支持 | ✅ 支持 |
到此这篇关于c++泛型编程详解的文章就介绍到这了,更多相关c++泛型编程内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论