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

条款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.lryc.cn/news/289266.html

相关文章:

  • 蓝桥杯省赛无忧 课件49 DFS-剪枝
  • Linux中查看端口被哪个进程占用、进程调用的配置文件、目录等
  • 大模型面试题总结
  • Authorization Failed You can close this page and return to the IDE
  • 【时间序列篇】基于LSTM的序列分类-Pytorch实现 part2 自有数据集构建
  • 《设计模式的艺术》笔记 - 策略模式
  • 【Elasticsearch篇】详解使用RestClient操作索引库的相关操作
  • ES数据处理方法
  • STM32实现软件IIC协议操作OLED显示屏(2)
  • 【linux】远程桌面连接到Debian
  • python222网站实战(SpringBoot+SpringSecurity+MybatisPlus+thymeleaf+layui)-菜单管理实现
  • JS之隐式转换与布尔判定
  • ubuntu20根目录扩容
  • (四)DQL数据查询语言
  • 网络安全03---Nginx 解析漏洞复现
  • 第十四届蓝桥杯C组题目 三国游戏
  • 【LeetCode-435】无重叠区间(贪心)
  • 写读后感的时候,可以适当地引用书中的内容吗?
  • RockChip DRM Display Driver
  • 【数据库】GaussDB数据类型和简单DDL概述
  • malloc/free和new/delete相关问题:
  • 设计一套扑克牌
  • ubuntu20.04 外接hdmi没有声音
  • Mybatis 拦截器注册方式
  • [嵌入式软件][启蒙篇][仿真平台] STM32F103实现SPI控制OLED屏幕
  • 个体诊所电子处方系统设计,社区门诊处方开单管理系统软件教程
  • 数据结构(1)--> 顺序表
  • 排序算法经典模型: 梯度提升决策树(GBDT)的应用实战
  • 【揭秘】ForkJoinTask全面解析
  • 如何利用数据压缩提高高性能存储的效率?