条款47:请使用traits classes表现类型信息

1.前言

STL主要由“用以表现容器,迭代器和算法”的template构成,但也覆盖若干工具性templates,其中一个名为advance,用来将某个迭代器移动某个给定距离:

tempalte<typename IterT,typename DistT>//将迭代器向前移动d单位
void advance(IterT& iter,DistT d);//如果d<0,则向后移动

观念上advance只是做iter+=d动作,但其实不可以全然是那样,因为只有random access(随机访问)迭代器才支持+=操作。面对其它迭代器种类,advance必须反复施行++或--,共d次。

先回顾下STL迭代器的分类:STL共有5种迭代器分类,inpiut迭代器只能向前移动,一次一步,客户可只读取它们所指的变量,而且只读取一次,他们模仿指向输入文件的阅读指针(read pointer);c++程序库中的istream_iterators是这一分类的代表。output迭代器情况类似,但一切只为输出:它们只向前移动,一次一步,客户只涂写一次。它们模仿指向输出文件的涂写指针(write pointer);ostream_iterators是这一分类的代表。这是功能最小的两个迭代器分类。由于这;两类都只能向前移动,而且只能读或写其所指物最多一次,所以它们只适合“一次性操作”(one-pass algorithms)。另一个功能比较强大的分类是forward迭代器。这种迭代器可以做前述两种分类所能做的每一件事,而且可以读或写其所指物一次以上。这使得它们可以施行于多次性操作算法(multi-pass algorithms)。STL并未提供单向linked list,但某些程序库有slit,而置入这种容器的迭代器就是属于forward迭代器。指入TR1 hashed容器的也可能是这一分类。

Bidirectional迭代器比上一个功能更大:它除了可以向前移动,还可以向后移动。STL的list迭代器就属于这一类,set,multiset,map和multimap的迭代器也都是这一分类。

最强大的迭代器当属random access迭代器。这种迭代器功能强大之处再与它可以执行“迭代器算术”,即可以在常量时间内向前或向后跳跃任意距离。

对这5种分类,c++标准程序库分别提供专属的卷标结构(tag struct)加以确认:

struct input_iterator_tag{};
struct output_iterator_tag{};
struct forward_iterator_tag:public input_iterator_tag{};
struct bidirectional_iterator_tag:public forward_iterator_tag{};
struct random_access_iterator_tag:public biairectional_iterator_tag{};

这些struct之间的继承关系是有效的is-a。

现在回到advance函数,我们知道STL迭代器有着不同的能力,实现advance的策略之一是采用“最低但最普及”的迭代能力,以循环往复递增或递减迭代器。然而这种做法耗费线性时间,我们知道random access迭代器支持迭代器算术运算,只耗费常量时间,如果面对这种迭代器,我们希望运用其优势:

然而我们真正希望的是以这种方式实现advance:

template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d)
{if(iterT& iter,DistT d){if(iter is a random access iterator){iter+=d;//针对random access迭代器使用迭代器算术运算}else{if(d>=0){while(d--) ++iter;}else{if(d>=0){while(d--) ++iter;}else {while(d++) --iter;}//针对其它迭代器分类,反复调用++或--}}}}

这种做法首先是必须判断iter是否未random access迭代器,也就是说需要知道类型IterT是否为random access迭代器。换句话说我们需要取得类型的某些信息。那就是traits让你得以进行的事情:它们允许你在编译期间取得某些类型信息。。

2.实例分析

traits并不是c++关键自或一个预先定义好的构件;他们是一种技术,也是一个c++程序员共同遵守的协议。这个技术的要求之一是他对内置(built-in)类型或用户自定义类型的表现必须一样好。举个例子,如果上述advance仍然必须有效运作,那意味这traits技术必须i能够施行于内置类型如指针上。

”traits必须能够施行于内置类型“意味”类型内的嵌套信息(nesting information)“这种东西必须出局了,因为我们无法将信息嵌套于原始指针内。因此类型的traits信息必须位于类型自身之外。标准技术时把它放进一个template及其一个被命名为iterator_traits:

template<typename iterT>//template,用来处理
struct iterator_traits;//迭代器分类的相关信息

正如所看到的,iterattor_traits是个struct。习惯上traits总是被实现为structs,但它们又往往被称为traits classes。

iterator_traits的运作方式是针对每一个类型IterT,针对每一个类型IterT,在struct iterator_traits<IterT>内一定声明某个typedef名为iiterator_category,这个typedef用来确认IterT的迭代器分类。

iterator_traits以两个部分实现上述所言。首先它要求每一个“用户自定义的迭代器类型”必须嵌套一个typedef,名为iterator_category,用来确认适当的卷标结构。例如deque的迭代器可随机访问,所以一个针对deque迭代器而设计的class看起来会是这个样子:

template<...>
class deque{public:class iterator{public:typedef random_access_iterator_tag  iterator_category;....};....
};

list的迭代器可双向行进,所以它们应该是这样的:

template<...>
class list{public:class iterator{public:typedef bidirectional_iterator_tag iterator_category;....};....
};

至于iterator_traits,只是相应iterator class的嵌套式typedef:

//类型IterT的iterator_category其实是用来表现“IterT说它自己是什么”
//关于“typedef typename”的运用,见条款42
template<typename IterT>
struct iterator_traits{typedef typename IterT::iterator_category iterator_category;...
};

这对用户自定义类型行得通,但对指针(也是一种迭代器)行不通,因为指针不可能嵌套tepedef。iterator_traits的第二部分如下,专门用来对付指针:

为了支持指针迭代器,iterator_traits特别针对指针类型提供一个偏特化版本(partial template specialization)。由于指针的行径与random access迭代器类似,所以iterator_traits为指针指定的迭代器类型是:

template<typename IterT>//template偏特化
struct iterator_traits<IterT*>//针对内置指针
{typedef random_access_iterator_tag  iterator_category;....
};

现在,我们应该了解了如何设计并实现一个traits class了:

(1)确认若干你希望可取的的类型相关信息。例如对迭代器而言,我们希望将来可取其分类(category);

(2)为该信息选择一个名称(比如iterator_category)

(3)提供一个template和一组特化版本,内含你希望支持的类型相关信息。

现在有了iterator_traits(实际上是std::iterator_traits,因为它是c++标准程序的一部分),我们可以对advance实践先前的伪码(pseudocode):

template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d)
{if(typeid(typename std::iterator_traits<IterT>::iterator_category)==typeid(std::random_access_iterator_tag))....
}

虽然这看起来前景光明,但并不是我们想要的。首先会导致编译问题。IterT类型在编译器间获知,所以iterator_traits<IterT>::iterator_category也可以在编译器间确定。但if语句却是在运行期才会核定。

我们真正想要的是一个条件式,判断“编译器核定成功”之类型。恰巧c++有一个取得这种行为的办法,那就是重载(overloading)。

当我们重载某个函数f,必须详细描述各个重载建房的参数类型。当你调用f,编译器便根据传来的实参选择最适合的重载件。编译器的态度是“如果这个重载件最匹配传递过来的实参,就调用这个f;如果那个重载件最匹配,就调用那个f;如果第三个f最匹配,就调用第三个f”。依次类推。这正是一个针对类型而发生的“编译器条件句”。为了让advance的行为如我们所期望的那样,我们需要做的是产生两版重载函数,内含advance的本质内容,但各自接受不同类型的iterator_category对象。我将这两个函数取名为doAdvance:

template<typename IterT,typename DistT>//用于random access 迭代器
void doAdvance(IterT& iter,typename Dist,std::random_access_iterator_tag)
{iter+=d;
}template<typename IterT,typename DistT,std::bidirectional_iterator_tag>//这份
{                                   //实现用于bidirectional迭代器if(d>=0){while(d--)++iter;}else{while(d++) --iter;}}
template<typename IterT,typename DistT>//这份用于实现input迭代器
void doAdvance(IterT& iter,DistT d,std::input_iterator_tag)
{if(d<0){throw std::out_of_range("Negative distance");}while(d--) ++iter;
}

由于forward_iterator继承自input_iterator_tag,所以上述doAdvance的input_iterator_tag版本也能够处理forward迭代器。这是iterator_tag structs继承关系带来的一项红利。实际上这也是public继承带来的部分好处:针对base class编写的代码用于derived class身上行得通。

advance函数规范说,如果面对的是random access和bidirectional迭代器,则接受正距离和负距离;但如果面对的是forward或input迭代器,则移动负距离会导致不明确(未定义)行为。我所检验过的实现码都假设d不为负,于是直接进入一个冗长的循环迭代,等待计数器降为0。上述异常我以抛出异常取而代之。

有了这些doAdvance重载版本,advance需要做的是调用它们并额外传递一个对象,后者必须带有适当的迭代器分类。于是编译器运用重载解析机制(overloading resolution)调用适当的实现代码:

template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d)
{doAdvance(iter,d,typename std::iterator_traits<IterT>::iterator_category());
}

现在我们可以总结如何使用一个traits class了:

(1)建立一组重载函数或函数模板,,彼此间的差异只在于各自的traits参数。令每个函数实现码与其接受之traits信息相应和。

(2)建立一个控制函数或函数模板,它调用上述那些劳工函数并传递traits class所提供的信息。

traits广泛用于标准程序库,其中当然有上述讨论的iterator_traits,除了供应iterator_category还供应另四分迭代器相关信息。此外还有char_traits用来保存字符类型相关信息,以及numeric_limits用来保存数值类型的相关信息,例如某些数值类型可表现之最小值和最大值等等;命名为numeric_limits有点让人惊讶,因为traits classes的名称以"traits"结束,但numeric_limits却没有遵守这种风格。

3.总结

(1)Traits classes使得“类型相关信息”在编译器可用。它们以template和“templates特化”完成实现。

(2)整合重载技术后,traits classes有可能在编译器对类型执行if...else 测试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/645429.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringMVC-异常处理

目录 HandlerExceptionResolver接口 使用注解实现异常分类管理(ControllerAdvice 和 ExceptionHandler) 使用 ControllerAdvice 对不同的 Controller 分别捕获异常并处理 HandlerExceptionResolver接口 在SpringMVC中&#xff0c;提供了一个全局异常处理器&#xff0c;用于…

特征抽取-----机器学习pycharm软件

导入包 from sklearn.datasets import load_iris # 方法datasets_demo()数据集使用 from sklearn.feature_extraction import DictVectorizer # 方法dict_demo()字典特征抽取用 from sklearn.feature_extraction.text import CountVectorizer # 方法count_demo()文本特征抽…

民用激光雷达行业简析

01. 激光雷达是“机器之眼” • 激光雷达是一个通过发射激光并接受发射激光同时对其进行信号处理&#xff0c;从而获得周边物体距离等信息的主动测量装置。 • 激光雷达主要由光发射、光扫描、光接收三大模块组成。光发射模块集成了驱动、开关和光源等芯片。光接收模块集成了…

【AIGC】Diffusers:扩散模型的开发手册说明2

前言 扩散器被设计成一个用户友好且灵活的工具箱&#xff0c;用于构建适合您用例的扩散系统。工具箱的核心是模型和调度程序。然而 DiffusionPipeline 为方便起见将这些组件捆绑在一起&#xff0c;但您也可以解包管道并分别使用模型和调度程序来创建新的扩散系统。 解构 Stab…

基于物联网的智能植物养护系统的设计与实现

基于物联网的智能植物养护系统的设计与实现 系统简介 由于大多数人喜欢花草却不会照料&#xff0c;买了花草之后却没有时间照顾&#xff0c;从而导致花草枯萎。本设计利用wifi传输设定植物的适当光照强度&#xff0c;适当的土壤温湿度。然后根据温湿度传感器及光照传感进行实时…

文件备份管理软件系统

1、我解决的问题 避免因为硬盘故障&#xff0c;导致数据丢失; 避免因为中了病毒&#xff0c;文件被加密&#xff0c;无法取回; 避免了员工恶意删除文件; 规范企业内部的文件管理&#xff0c;使它井井有条; 防范于未然&#xff0c;不必再为可能的风险担忧; 2、我的优点 我支持定…

第二篇【传奇开心果系列】beeware的toga开发移动应用示例:手机应用视频播放器

传奇开心果博文系列 系列博文目录beeware的toga开发移动应用示例系列 博文目录一、项目目标二、编程思路三、初步实现项目目标示例代码四、第一次扩展示例代码五、第二次扩展示例代码六、第三次扩展示例代码七、第四次扩展示例代码八、第五次扩展示例代码九、第六次扩展示例代码…

1.19号网络

超时检测 概念 1> 在网络通信中&#xff0c;有很多函数是阻塞函数&#xff0c;会导致进程的阻塞&#xff0c;例如&#xff1a;accept、recv、recvfrom、等等 2> 为了避免进程在阻塞函数处&#xff0c;无休止的等待&#xff0c;我们可以设置一个超时时间&#xff0c;当…

详细分析MybatisPlus中的Page类(附实战)

目录 前言1. 基本知识2. 常用方法3. 实战 前言 由于工作中经常使用到MybatisPlus的框架&#xff0c;对此详细连接Page类有利于开发&#xff0c;更加游刃有余 对于该类的源码&#xff1a;baomidou / mybatis-plus 中的Page源码 MybatisPlus的框架&#xff1a;MyBatis-plus从入…

【操作系统基础】【CPU访存原理】:寄存 缓存 内存 外存、内存空间分区、虚拟地址转换、虚拟地址的映射

存储器怎么存储数据、内存空间分区、虚拟地址转换 计算机的存储器&#xff1a;寄存 缓存 内存 外存&#xff08;按功能划分&#xff09; 计算机的处理器需要一个存储器来存储大量的指令和数据以便自己不断取指执行和访问数据。 内存&#xff08;内存就是运行内存&#xff0c…

java web 校园健康管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java Web校园健康管理系统是一套完善的java web信息管理系统 &#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysq…

【AI】深度学习与图像描述生成——看图说话(2)

目录 一、计算机视觉 应用场景 重要意义 二、自然语言处理 应用场景 重要意义 三、二者的联系与结合 联系 结合场景 重要意义 四、图像描述处理&#xff08;生成&#xff09; 关键技术 发展历程 五、一些补充 计算机视觉和自然语言处理是人工智能领域的两大重要分…

查询列表实时按照更新时间降序排列 没有更新时间就按创建时间

例子: sql两个字段排序 ORDER BY update_time DESC , create_time DESC <select id"selectLawIllegalActivitiesList" parameterType"LawIllegalActivities" resultMap"LawIllegalActivitiesResult"><include refid"selectL…

252.【2023年华为OD机试真题(C卷)】局域网中的服务器个数(优先搜索(DFS)-JavaPythonC++JS实现)

🚀点击这里可直接跳转到本专栏,可查阅顶置最新的华为OD机试宝典~ 本专栏所有题目均包含优质解题思路,高质量解题代码(Java&Python&C++&JS分别实现),详细代码讲解,助你深入学习,深度掌握! 文章目录 一. 题目-局域网中的服务器个数二.解题思路三.题解代码P…

04.Elasticsearch应用(四)

Elasticsearch应用&#xff08;四&#xff09; 1.目标 这一章主要解读以下索引 2.什么是索引 索引是文档的容器&#xff0c;是一类文档的结合索引是一个逻辑命名空间&#xff0c;它映射到一个或多个主分片&#xff0c;并且可以具有零个或多个副本分片索引中数据分散在Shard…

宠物空气净化器怎么挑选?猫用空气净化器品牌性比价推荐

作为一个养猫家庭的主人&#xff0c;每天都要面对一个挑战——清理猫砂盆。那种难以形容的气味实在让人受不了。尤其是家里有小孩和老人&#xff0c;他们偶尔可能会出现过敏性鼻炎等问题&#xff0c;而抵抗力较差的人更容易受到影响。此外&#xff0c;一到换毛季节&#xff0c;…

【基础算法练习】二分模板

文章目录 二分模板题二分的思想C 版本的二分整数二分模板 Golang 版本的二分整数二分模板 例题&#xff1a;在排序数组中查找元素的第一个和最后一个位置题目描述C 版本代码Golang 版本代码 二分模板题 704. 二分查找&#xff0c;这道题目是最经典的二分查找&#xff0c;使用于…

Spring依赖注入之setter注入与构造器注入以及applicationContext.xml配置文件特殊值处理

依赖注入之setter注入 在管理bean对象的组件的时候同时给他赋值&#xff0c;就是setter注入&#xff0c;通过setter注入&#xff0c;可以将某些依赖项标记为可选的&#xff0c;因为它们不是在构造对象时立即需要的。这种方式可以减少构造函数的参数数量&#xff0c;使得类的构…

天津大数据培训班推荐,数据分析过程的常见错误

大数据”是近年来IT行业的热词&#xff0c;目前已经广泛应用在各个行业。大数据&#xff0c;又称海量信息&#xff0c;特点是数据量大、种类多、实时性强、数据蕴藏的价值大。大数据是对大量、动态、能持续的数据&#xff0c;通过运用分析、挖掘和整理&#xff0c;实现数据信息…

【dpdk】Getting Started Guide for Linux DPDK

Getting Started Guide for Linux — Data Plane Development Kit 23.11.0 documentation (dpdk.org) DPDK官网 文章目录 1.dpdk build with isa-l2.System Requirements3.Running DPDK Applications3.1. dpdk-hugepages Application3.1.1. Running the Application3.1.2. Opt…