当前位置: 首页 > 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.jsqmd.com/news/868287/

相关文章:

  • Cadence AMS数模混合仿真保姆级教程:从Virtuoso环境搭建到仿真加速全流程
  • VLC隐藏玩法:结合Lua脚本实现智能视频播放(比如根据时间切换片单)
  • 告别云端:用Llama.cpp+Q4量化模型,在Jetson Orin Nano上打造你的私有AI助手
  • FastbootEnhance:Windows平台终极Fastboot工具箱与Payload提取器完整指南
  • 基于SpringBoot2+vue2的流浪宠物管理系统
  • Multi-Agent系统的高可用架构:容灾设计、故障隔离与快速恢复方案
  • 告别数据混乱!用腾讯TBDS的数据血缘与数据地图,5分钟理清你的数据资产
  • 如何使用FinalShell远程管理Linux云服务器?
  • 避坑指南:MMSegmentation自定义数据集训练时,如何解决‘xxxDataset is not in the dataset registry’等5个常见报错
  • ArcGIS Desktop 10.2 安装后必做的5件事:从激活分析拓展到优化地图性能
  • C#与Unity 3D构建100ms级工业数字孪生系统
  • 用ESP32-S3和花生壳内网穿透,5分钟搞定远程宠物/植物监控摄像头
  • 从张宇的课到代码实战:用Python和MATLAB手把手搞定分数阶求导(附完整代码)
  • 三年级下册语文第三单元作文:我做了一个小实验300字
  • Nature 正刊丨向蜜蜂偷师,教会了无人机长距离精准导航,内存只需42KB!
  • Stata面板数据回归保姆级教程:从xtset到豪斯曼检验,手把手搞定实证分析
  • 【c++面向对象编程】第46篇:CRTP(奇异递归模板模式):静态多态的妙用
  • 别再乱买充电头了!一文看懂USB PD协议,教你选对笔记本和手机的‘能量搭档’
  • 从炼丹到炼蛋白:手把手拆解AlphaFold2的模型架构与训练技巧
  • 新高考答题卡模板全套PDF可打印(语文数学英语等)
  • 告别Ground Truth!用U2Fusion这个无监督网络,搞定多模态图像融合(附RoadScene数据集)
  • 2026年评价高的LED 薄膜开关/东莞定制薄膜开关厂家综合对比分析 - 行业平台推荐
  • Klogg实战:5分钟搞定海量日志中的Error排查(颜色标记+正则过滤技巧)
  • 告别编译限制!手把手教你用注册机破解Keil5 MDK(附2032年有效CID生成方法)
  • 手把手教你用C语言写一个简易的SMTP邮件内容解析器(基于libnids抓包库)
  • 别再只调样式了!深入理解鸿蒙ArkTS中Slider的四种交互状态(Begin/Moving/End/Click)
  • 2026年4月市面上评价好的建筑加固服务厂家推荐,建筑加固/建筑结构检测/建筑结构胶,建筑加固服务商推荐 - 品牌推荐师
  • 告别英文界面:RedHat Enterprise Linux 6.3 中文语言包配置与常见问题排查
  • ESP32 + SPH0645麦克风:用Python在电脑上实时播放音频的保姆级教程(附避坑指南)
  • 别再只会用PWM调速度了!STM32驱动直流有刷电机,H桥的三种模式(单极/双极/受限)到底怎么选?