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

STL 源码阅读笔记-类型萃取(Traits)

简介

Traits,是一种约定俗称的技术方案,在STL中广泛被使用。常常被用于使不同的类型可以用于相同的操作 , 或者针对不同类型提供不同的实现。

笔记基本是根据《STL源码剖析》上第三章的内容进行总结。

抛出问题

在算法中使用迭代器的时候,会用到迭代器的相关类型。假设现在需要声明一个变量,这个变量的类型是“迭代器所指向的对象的类型”(这个属于迭代器相关类型之一),那么应该怎么做?

function template的解决方式

利用function template的方式可以做到这一点

template<class I, class T>
void func_imp(I iterator, T t){
	T tmp; // 这个tmp就是迭代器指向的类型

	// ........ 其他操作
}    

template<class I>
void func(I iterator){
	func_imp(I, *I);
}

int main(){
	func(&100);
}

虽然利用function template的方式解决了之前的问题,但存在一些其他问题:

  1. “迭代器所指向的对象的类型”只是迭代器相关类型中的其中一种,而其他相关类型并不能完全按这种方式生成。这个问题后续会有更详细的解释。
  2. 如何在func中返回 “迭代器所指向的对象的类型”

Traits编程技法

我们把“迭代器所指向的对象的类型”称为value_type, 如何将value_type在func中返回是我们的问题。

声明内嵌类型可以解决上述问题

template <class T>
class MyIter{
	typedef T    value_type;
	T* ptr;
}

 template<class I>
 typename I::value_type
 func(I iterator){
 	return *I;
 }

MyIter<int> it();
it->ptr = new int(10);

func(it);

假设我们传入的迭代器对象中有一个内嵌的value_type,那么我们就可以在func的返回值上填上I::vlaue_type,这样就返回正确的类型了。但这样的话原生指针类型就有问题了,因为原生指针是不存在value_type的。

但我们可以通过偏特化的方式,专门为原生类型的指针编写一个函数,这样在调用的时候,编译器就会有限匹配特化后的版本

 template<class I>
I func(I* iterator){
 	return *I;
 }

有了上面的思路,我们可以更进一步,编写一个专门获取value_type的模板类。

template<class I>
struct iterator_traits{
	typedef typename I::value_type value_type;
}

template<class I>
struct iterator_traits<I *>{
	typedef I  value_type;
}

这样对于之前的func内容就可以变成

 template<class I>
 typename iterator_traits<I>::value
 func(I iterator){
 	return *I;
 }

这样返回值类型就统一了,不必额外特化一个新的模板函数。

迭代器的相关类型

在这里插入图片描述
迭代器中会有5个常用的相关类型:

  1. value_type 表示迭代器所指对象的类型
  2. pointer 表示迭代器所指对象的指针类型
  3. reference 表示迭代器所指对象的引用类型
  4. difference_type 表示两个迭代器之间的距离的类型
  5. iterator_category 表示迭代器的类型,这个类型只会有5类。

接上之前function template的存在问题1,这5个相关类型并不是每个都可以像func template中获取value_type那样获取到的对应的类型的。

iterator_category

iterator_category 分为5类:

  1. input_iterator 迭代器对象不允许被外界改变,相当于只读
  2. output_iterator 迭代器对象只写
  3. forward_iterator 迭代器对象可以读写
  4. bidirectional_iterator 迭代器对象可双向移动
  5. random_access_iterator 前三种迭代器对象支持operator ++, 第四种额外支持 operator --, 而第五种支持 +n, -n 这种操作。

在这里插入图片描述

Traits能将运行期的行为变成编译期

假设我们有一个函数func,他对不同的迭代器类型有不同的操作

template<class I>
void func(I& i){
	if(I::iterator_category == forward_iterator)
		func_forward_iterator(i);
	elif(I::iterator_category == bidirectional_iterator )
		func_bidirectional_iterator(i);
	elif(I::iterator_category == random_access_iterator  )
		func_random_access_iterator(i);
}

虽然这样也能运行,但这个方式是在运行的时候通过判断类型的方式来决定执行的函数,会影响运行时效率。

Traits能够将这个行为提前到编译期,让编译器在编译的时候就选择好正确的执行版本

struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
……
// 继承能满足上述迭代器分类关系
	
template<class I>
void func(I& i)
{
    typedef typename Iterator_traits<Iterator>::iterator_category category;  //tratis 提取出对应的category
    __func(i, category()); // 各型别的重载
}

template<class I>
__func(I& i, input_iterator_tag ){
	//func_forward_iterator(i) 的实现
}

template<class I>
__func(I& i, bidirectional_iterator ){
	//func_bidirectional_iterator(i) 的实现
}
……

通过tratis 提取出对应的category以及重载不同参数的__func函数,就可以在编译期确定具体调用的函数。

更多例子

  1. std::advance
  2. std::distance

总结

trais这个技巧大量运用在STL中,它利用的是内嵌类型和编译器template参数推导等特性,实现对自定义数据和编译器内置数据的统一,同时带来的好处还有统一外部接口,降低代码冗余、提高运行时效率等等。

参考:

  1. 《STL源码剖析》
  2. https://www.cnblogs.com/Tonarinototoro/p/14037845.html
  3. https://www.cnblogs.com/mangoyuan/p/6446046.html

相关文章:

  • 【MySQL 第三天数据库表 增删改查】
  • 【白板推导系列笔记】降维-样本均值样本方差矩阵
  • nonebot2聊天机器人插件10:迁移至nonebot2.0.0b5
  • 高速度结构设计
  • Java(六)——常用类 --- 大数运算
  • 【对比Java学Kotlin】协程-异步流
  • 基于Vue+SSM+SpringCloudAlibaba的英雄管理系统
  • 基于注意力机制的循环神经网络对 金融时间序列的应用 学习记录
  • 女篮亚军,为啥男篮那么水?
  • 发动机曲轴及曲柄连杆机构受力有限元分析
  • 进程概念(Linux)
  • 国庆弯道超车之最长递增子序列衍生的一类题
  • 30. Python 修改列表的元素
  • Redis入门-下载-安装-启动服务测试
  • 一个C#开发的、跨平台的服务器性能监控工具
  • [NodeJS] 关于Buffer
  • 【跃迁之路】【733天】程序员高效学习方法论探索系列(实验阶段490-2019.2.23)...
  • angular2 简述
  • JavaScript 基础知识 - 入门篇(一)
  • js ES6 求数组的交集,并集,还有差集
  • log4j2输出到kafka
  • node和express搭建代理服务器(源码)
  • node入门
  • Python学习之路16-使用API
  • quasar-framework cnodejs社区
  • Tornado学习笔记(1)
  • vue-router的history模式发布配置
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 分布式任务队列Celery
  • 学习JavaScript数据结构与算法 — 树
  • - 转 Ext2.0 form使用实例
  • 阿里云ACE认证之理解CDN技术
  • 测评:对于写作的人来说,Markdown是你最好的朋友 ...
  • 你学不懂C语言,是因为不懂编写C程序的7个步骤 ...
  • #微信小程序(布局、渲染层基础知识)
  • (ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY)讲解
  • (附源码)springboot高校宿舍交电费系统 毕业设计031552
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • *p=a是把a的值赋给p,p=a是把a的地址赋给p。
  • .bat批处理(五):遍历指定目录下资源文件并更新
  • .NET BackgroundWorker
  • .net6使用Sejil可视化日志
  • .NET导入Excel数据
  • .NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2
  • .Net下使用 Geb.Video.FFMPEG 操作视频文件
  • .vimrc php,修改home目录下的.vimrc文件,vim配置php高亮显示
  • @EnableConfigurationProperties注解使用
  • [ SNOI 2013 ] Quare
  • [ vulhub漏洞复现篇 ] Apache Flink目录遍历(CVE-2020-17519)
  • [④ADRV902x]: Digital Filter Configuration(发射端)
  • [AR Foundation] 人脸检测的流程
  • [BJDCTF 2020]easy_md5
  • [c++] C++多态(虚函数和虚继承)
  • [C++]拼图游戏
  • [CareerCup] 13.1 Print Last K Lines 打印最后K行