【c++面向对象编程】第45篇:萃取(Traits)技术与策略类:STL源码中的智慧
目录
一、问题:如何同时处理指针和迭代器?
cpp
// 想写一个 advance 函数,让迭代器前进 n 步
template <typename Iter>
void myAdvance(Iter& it, int n) {
// 如果是随机访问迭代器:it += n
// 如果是单向迭代器:循环 n 次 ++it
// 如何区分?
}
STL 的迭代器分为不同类型:
-
input_iterator_tag(只读单向) -
forward_iterator_tag(单向读写) -
bidirectional_iterator_tag(双向) -
random_access_iterator_tag(随机访问)
Traits 方案:通过 iterator_traits 获取迭代器的类型信息,编译期选择算法。
cpp
#include <iterator>
template <typename Iter>
void myAdvance(Iter& it, int n) {
using category = typename std::iterator_traits<Iter>::iterator_category;
myAdvanceImpl(it, n, category()); // 根据迭代器类型重载
}
// 随机访问迭代器
template <typename Iter>
void myAdvanceImpl(Iter& it, int n, std::random_access_iterator_tag) {
it += n;
}
// 双向迭代器
template <typename Iter>
void myAdvanceImpl(Iter& it, int n, std::bidirectional_iterator_tag) {
if (n > 0) while (n--) ++it;
else while (n++) --it;
}
// 单向迭代器
template <typename Iter>
void myAdvanceImpl(Iter& it, int n, std::forward_iterator_tag) {
while (n--) ++it;
}
二、iterator_traits 的实现原理
iterator_traits 是一个类模板,用于提取迭代器的属性。
cpp
// 泛型版本:适用于标准迭代器
template <typename Iter>
struct iterator_traits {
using iterator_category = typename Iter::iterator_category;
using value_type = typename Iter::value_type;
using difference_type = typename Iter::difference_type;
using pointer = typename Iter::pointer;
using reference = typename Iter::reference;
};
// 特化版本:适用于普通指针(T*)
template <typename T>
struct iterator_traits<T*> {
using iterator_category = random_access_iterator_tag;
using value_type = T;
using difference_type = ptrdiff_t;
using pointer = T*;
using reference = T&;
};
// 特化版本:适用于 const 指针(const T*)
template <typename T>
struct iterator_traits<const T*> {
using iterator_category = random_access_iterator_tag;
using value_type = T; // 注意:const T* 的 value_type 是 T,不是 const T
using difference_type = ptrdiff_t;
using pointer = const T*;
using reference = const T&;
};
核心思想:
-
对于标准迭代器,直接使用迭代器内部定义的
iterator_category -
对于普通指针,提供特化版本,让指针也能“伪装”成随机访问迭代器
-
STL 算法通过
iterator_traits获取信息,与容器解耦
三、自定义 traits 类:获取容器的元素类型
假设我们要写一个通用函数,获取容器的元素类型:
cpp
// 方案1:直接使用 value_type(要求容器符合 STL 规范)
template <typename Container>
struct ElementType {
using type = typename Container::value_type;
};
// 方案2:支持数组
template <typename T, size_t N>
struct ElementType<T[N]> {
using type = T;
};
// 方案3:支持裸指针(视为单元素)
template <typename T>
struct ElementType<T*> {
using type = T;
};
// 使用
template <typename Container>
void process(const Container& c) {
typename ElementType<Container>::type x; // 编译期得到元素类型
cout << "元素类型: " << typeid(x).name() << endl;
}
更完整的版本(类似 std::iterator_traits):
cpp
#include <vector>
#include <list>
#include <array>
#include <typeinfo>
#include <iostream>
using namespace std;
// 主模板:假设有 value_type
template <typename T>
struct ValueTypeOf {
using type = typename T::value_type;
};
// 特化:数组
template <typename T, size_t N>
struct ValueTypeOf<T[N]> {
using type = T;
};
// 特化:普通指针
template <typename T>
struct ValueTypeOf<T*> {
using type = T;
};
// 辅助 using 别名(C++11)
template <typename T>
using ValueTypeOf_t = typename ValueTypeOf<T>::type;
// 通用打印函数
template <typename Container>
void printFirst(const Container& c) {
using ElementType = ValueTypeOf_t<Container>;
// 注意:这里简化了,实际需要检查容器是否为空
cout << typeid(ElementType).name() << endl;
}
int main() {
vector<int> v;
list<double> l;
array<string, 5> a;
int arr[10];
int* p = arr;
printFirst(v); // int
printFirst(l); // double
printFirst(a); // string
printFirst(arr); // int
printFirst(p); // int
return 0;
}
四、类型函数:编译期计算类型
Traits 的核心是类型函数——输入一个类型,输出另一个类型或值。
示例1:移除 const
cpp
template <typename T>
struct RemoveConst {
using type = T;
};
template <typename T>
struct RemoveConst<const T> {
using type = T;
};
template <typename T>
using RemoveConst_t = typename RemoveConst<T>::type;
// 使用
RemoveConst_t<const int> x = 42; // x 是 int
示例2:移除引用
cpp
template <typename T>
struct RemoveReference {
using type = T;
};
template <typename T>
struct RemoveReference<T&> {
using type = T;
};
template <typename T>
struct RemoveReference<T&&> {
using type = T;
};
// 使用
RemoveReference_t<int&> x = 10; // x 是 int
示例3:条件选择(编译期 if)
cpp
template <bool Cond, typename TrueType, typename FalseType>
struct Conditional {
using type = TrueType;
};
template <typename TrueType, typename FalseType>
struct Conditional<false, TrueType, FalseType> {
using type = FalseType;
};
// 使用
Conditional_t<(sizeof(int) > sizeof(char)), int, char> x;
// 如果 int 大于 char,x 是 int;否则是 char
C++11 标准库已经提供了这些:<type_traits> 头文件。
cpp
#include <type_traits> using T1 = std::remove_const<const int>::type; // int using T2 = std::remove_reference<int&>::type; // int using T3 = std::conditional<true, int, double>::type; // int static_assert(std::is_same<T1, int>::value, "相同");
五、constexpr 与 traits 的关系
C++11 引入的 constexpr 可以计算编译期值,部分替代 traits 的“值萃取”。
传统 traits:编译期值
cpp
// 判断是否为指针(值萃取)
template <typename T>
struct IsPointer {
static constexpr bool value = false;
};
template <typename T>
struct IsPointer<T*> {
static constexpr bool value = true;
};
// 使用
if constexpr (IsPointer<int*>::value) {
cout << "是指针" << endl;
}
C++17 变量模板简化
cpp
template <typename T>
inline constexpr bool is_pointer_v = IsPointer<T>::value;
// 使用
if constexpr (is_pointer_v<int*>) { ... }
constexpr 函数可以替代简单的 traits
cpp
// 编译期求阶乘
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int arr[factorial(5)]; // 编译期计算,arr[120]
// 但 constexpr 函数不能处理类型操作(如判断是否为指针)
// 类型操作仍然需要 traits
| 能力 | traits | constexpr |
|---|---|---|
| 类型变换(如移除 const) | ✅ | ❌ |
| 编译期值计算 | ✅ | ✅ |
| 类型判断(如 is_pointer) | ✅ | ❌ |
| 编译期分支选择 | ✅(特化) | ✅(if constexpr) |
六、策略类(Policy Classes)
Traits 用于描述“是什么”,策略类用于描述“怎么做”。
cpp
// 策略类:定义排序方式
struct Ascending {
template <typename T>
static bool compare(const T& a, const T& b) {
return a < b;
}
};
struct Descending {
template <typename T>
static bool compare(const T& a, const T& b) {
return a > b;
}
};
// 算法类:接受策略作为模板参数
template <typename T, typename Policy = Ascending>
class Sorter {
public:
static void sort(std::vector<T>& vec) {
for (size_t i = 0; i < vec.size(); i++) {
for (size_t j = i + 1; j < vec.size(); j++) {
if (Policy::compare(vec[j], vec[i])) {
std::swap(vec[i], vec[j]);
}
}
}
}
};
// 使用
vector<int> v = {3, 1, 4, 1, 5};
Sorter<int, Ascending>::sort(v); // 升序
Sorter<int, Descending>::sort(v); // 降序
Traits vs Policy:
| 维度 | Traits | Policy |
|---|---|---|
| 关注点 | 对象“是什么” | 算法“怎么做” |
| 典型用途 | iterator_traits | 排序策略、分配器 |
| 获取方式 | 通过类型萃取 | 作为模板参数传入 |
七、完整例子:自定义 iterator_traits
cpp
#include <iostream>
#include <vector>
#include <list>
#include <iterator>
#include <type_traits>
using namespace std;
// 自定义的 iterator_traits(简化版)
template <typename Iter>
struct MyIteratorTraits {
using value_type = typename Iter::value_type;
using difference_type = typename Iter::difference_type;
using iterator_category = typename Iter::iterator_category;
};
// 特化:普通指针
template <typename T>
struct MyIteratorTraits<T*> {
using value_type = T;
using difference_type = ptrdiff_t;
using iterator_category = random_access_iterator_tag;
};
// 打印迭代器类型名称的辅助函数
template <typename Iter>
void printIteratorInfo(const Iter& it) {
using traits = MyIteratorTraits<Iter>;
using category = typename traits::iterator_category;
cout << "迭代器类型: ";
if (is_same<category, random_access_iterator_tag>::value) {
cout << "随机访问";
} else if (is_same<category, bidirectional_iterator_tag>::value) {
cout << "双向";
} else if (is_same<category, forward_iterator_tag>::value) {
cout << "单向";
} else {
cout << "输入";
}
cout << endl;
}
// 使用 traits 的 distance 实现
template <typename Iter>
typename MyIteratorTraits<Iter>::difference_type
myDistance(Iter first, Iter last) {
using category = typename MyIteratorTraits<Iter>::iterator_category;
return myDistanceImpl(first, last, category());
}
// 随机访问迭代器
template <typename Iter>
typename MyIteratorTraits<Iter>::difference_type
myDistanceImpl(Iter first, Iter last, random_access_iterator_tag) {
cout << "[随机访问版本] ";
return last - first;
}
// 非随机访问迭代器
template <typename Iter>
typename MyIteratorTraits<Iter>::difference_type
myDistanceImpl(Iter first, Iter last, forward_iterator_tag) {
cout << "[循环版本] ";
typename MyIteratorTraits<Iter>::difference_type n = 0;
while (first != last) {
++first;
++n;
}
return n;
}
int main() {
// vector 是随机访问迭代器
vector<int> vec = {1, 2, 3, 4, 5};
cout << "vector distance: " << myDistance(vec.begin(), vec.end()) << endl;
// list 是双向迭代器(退化到循环版本)
list<int> lst = {1, 2, 3, 4, 5};
cout << "list distance: " << myDistance(lst.begin(), lst.end()) << endl;
// 普通指针也是随机访问
int arr[] = {1, 2, 3, 4, 5};
cout << "array distance: " << myDistance(arr, arr + 5) << endl;
return 0;
}
输出:
text
[随机访问版本] vector distance: 5 [循环版本] list distance: 5 [随机访问版本] array distance: 5
八、常见错误
1. 忘记特化导致指针类型无法使用
cpp
// ❌ 泛型版本要求 T::value_type,指针没有
template <typename T>
void process(T t) {
typename T::value_type x; // 传入 int* 时编译错误
}
2. 在 traits 中使用运行时条件
traits 必须在编译期确定,不能依赖运行时数据。
3. 混淆 typename 位置
cpp
// ❌ 错误 typename T::iterator it; // ✅ 正确 typename T::iterator it; // 但变量声明前要加 typename,类型定义时也要注意
九、这一篇的收获
你现在应该理解:
-
Traits(萃取):编译期获取类型信息的机制,通过模板特化实现
-
iterator_traits:让算法统一处理迭代器和指针,STL 的核心设计 -
类型函数:输入类型,输出类型(如
remove_const)或值(如is_pointer) -
constexpr:可替代部分值萃取,但不能替代类型变换 -
策略类:把算法中的可变行为作为模板参数,与 traits 互补
💡 小作业:实现一个
is_same类型萃取,编译期判断两个类型是否相同。实现enable_if的简化版本,用于 SFINAE。
下一篇预告:第46篇《CRTP(奇异递归模板模式):静态多态的妙用》——派生类把自己作为模板参数传给基类,实现编译期多态,避免虚函数开销。CRTP 是 C++ 中一个巧妙的惯用法,广泛用于静态多态、对象计数、混入类等场景。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)