当前位置: 首页 > news >正文

【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;否则是 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
能力traitsconstexpr
类型变换(如移除 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

维度TraitsPolicy
关注点对象“是什么”算法“怎么做”
典型用途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++ 中一个巧妙的惯用法,广泛用于静态多态、对象计数、混入类等场景。

http://www.gsyq.cn/news/1351970.html

相关文章:

  • 别再只用Graphics2D了!5个Java图片缩放方案实战评测:从Thumbnailator到OpenCV,谁画质最好?
  • 基于SpringBoot2+vue2的流浪宠物管理系统
  • ArcGIS Desktop 10.2 安装后必做的5件事:从激活分析拓展到优化地图性能
  • 2026年如何开通小程序店铺
  • 锂电池健康评估:避开NASA/Oxford数据IC分析中的三个常见坑(滤波、异常值、容量增生)
  • 使用Taotoken后我的大模型API月度账单清晰可见
  • 别再踩坑了!Windows 10下CUDA 11.3与cuDNN 8.2.1保姆级配置指南(附TensorFlow/PyTorch版本对照表)
  • 蓝桥杯嵌入式实战:手把手教你用STM32CubeMX和HAL库封装PWM控制函数(调频调占空比)
  • 从炼丹到炼蛋白:手把手拆解AlphaFold2的模型架构与训练技巧
  • 2026年评价高的LED 薄膜开关/东莞定制薄膜开关厂家综合对比分析 - 行业平台推荐
  • 食品车间阁楼平台这样搭,一次通过SC审查不走弯路
  • 保姆级教程:SAP资产折旧调错了怎么办?手把手教你用AB08和反向事务类型回退操作
  • 不只是烧录:用Jetson Orin Nano + OpenCV 4.4.0 + ROS Noetic搭建你的第一个边缘视觉AI项目
  • 基于SpringBoot2+vue2的智能学习平台系统
  • Ubuntu 20.04离线安装ntpdate保姆级教程(附arm64/amd64包下载与依赖解决)
  • 具身智能数据标注工具对比评测:6大平台横向测评
  • 从仿真翻车到波形完美:手把手教你用Multisim搞定LM741反相放大电路(含电源/电容配置避坑)
  • 别再只盯着STM32了!聊聊TI MSP430F149在低功耗电赛项目中的实战优势与配置细节
  • 别再只会用PWM调速度了!STM32驱动直流有刷电机,H桥的三种模式(单极/双极/受限)到底怎么选?
  • Stata面板数据回归保姆级教程:从xtset到豪斯曼检验,手把手搞定实证分析
  • VLC隐藏玩法:结合Lua脚本实现智能视频播放(比如根据时间切换片单)
  • 2026年评价高的刀片/韩国LONGYI刀片长期合作厂家推荐 - 品牌宣传支持者
  • 从CDDT模板到CDD数据库:手把手教你为车门ECU定制诊断描述文件
  • 避坑指南:IBM V5000存储初始化时遇到的CMMVC8020E报错怎么解决?
  • STM32F407 PWM呼吸灯实战:从CubeMX配置到代码调试,手把手教你玩转TIM14
  • 新高考答题卡模板全套PDF可打印(语文数学英语等)
  • 三年级下册语文第三单元作文:我做了一个小实验300字
  • Multi-Agent系统的高可用架构:容灾设计、故障隔离与快速恢复方案
  • Cadence AMS数模混合仿真保姆级教程:从Virtuoso环境搭建到仿真加速全流程
  • Qt Designer里那个神秘的‘控件提升’到底怎么用?手把手教你把Matplotlib画布嵌进去