【c++面向对象编程】第45篇:萃取(Traits)技术与策略类:STL源码中的智慧
目录
一、问题:如何同时处理指针和迭代器?
二、iterator_traits 的实现原理
三、自定义 traits 类:获取容器的元素类型
四、类型函数:编译期计算类型
示例1:移除 const
示例2:移除引用
示例3:条件选择(编译期 if)
五、constexpr 与 traits 的关系
传统 traits:编译期值
C++17 变量模板简化
constexpr 函数可以替代简单的 traits
六、策略类(Policy Classes)
七、完整例子:自定义 iterator_traits
八、常见错误
1. 忘记特化导致指针类型无法使用
2. 在 traits 中使用运行时条件
3. 混淆 typename 位置
九、这一篇的收获
一、问题:如何同时处理指针和迭代器?
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;否则是 charC++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++ 中一个巧妙的惯用法,广泛用于静态多态、对象计数、混入类等场景。
