20180404 C++ 请使用特质类(traits classes)表现类型信息
STL主要由“用以表现容器、迭代器和算法”的templates构成,但也覆盖若干工具性模板,其中一个名为advance,用来将某个迭代器移动某个特定距离:
template<typename IterT,typename DistT> //将迭代器向前移动d单位
void advance(IterT& iter,DistT d); //若d<0 则向后移动。
观念上advance只是做iter+=d动作,但其实不可以全然那么实践,因为只有随机访问(random access)迭代器才支持+=操作,面对其他威力不那么强大的迭代器种类,advance必须反复执行++或--,共d次。
复习STL的迭代器分类(共5种):
(1)Input迭代器:只能向前移动,一次一步,客户只能读取(不能修改)他们所指的东西,而且只能读取一次,他们模仿指向输入文件的阅读指针(read pointer);C++程序库中的istream_iterators是这一分类的代表;
(2)Output迭代器情况类似,但一切只为输出,另外客户只能修改他们所指的东西,而且只能修改一次,他们模仿指向输出文件的修改指针(write pointer);ostream_itetators是这一分类的代表。
上述两种迭代器威力较小(只能向前移动),所以只适合“一次性操作算法(one-pass algorithms)”。
(3)forward迭代器:这种迭代器可以做前述所有的操作,而且可以读或写所指物一次以上,这使得它们可施行于多次性操作算法。
(4)Bidirectional迭代器:威力更强大,它除了可以向前移动,还可以向后移动,STL的list迭代器就属于这一类,set,multiset,map,和multimap的迭代器也都属于这一类。
(5)随机访问迭代器:威力最大,它可以执行迭代器运算,即它可以在常量时间内向前或向后跳跃任意距离。vector,deque,和strig提供的迭代器就是这一分类。
对于以上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 bidirectional_iterator_tag{};
这些structs之间的继承关系是有效的is-a关系,所有的forward迭代器都是input迭代器,依此类推。
我们希望使用如下方式实现advance:
template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d)
{
if(iter is a random access iterator)
{
iter += d; //针对random access迭代器使用迭代器算数运算
}
else
{
if(D >= 0){while(d--) ++iter;} //针对其他迭代器分类反复调用++或--
else {while(d++) --iter;}
}
}
这种方法首先必须判断iter是否为随机访问迭代器,traits会帮你办到。
标准技术将类型的traits信息放进一个template及其一个或多个特化版本中,这样的templates在标准程序库中有若干个,其中针对迭代器者被命名为iterator_traits:
template<typename IterT> //模板,用来处理迭代器分类的相关信息
struct iterator_traits;
iterator_traits的运作方式是,针对每一个类型IterT,在结构体iterator_traits<IterT>里一定声明某个typedef名为iterator_category。这个typedef用来确认Itert的迭代器分类。
以下是对advance实践先前的伪码(pseudocode):
typename<typename IterT,typename DistT>
void advance(IterT& iter,Dist d)
{
if(typeid(typename std::iterator_traits<IterT>::iterator_category)
== typeid(std::random_access_iterator_tag))
...
}
上述代码会导致编译问题。
我们真正想要的是一个条件式(即if...else语句)判断“编译期核定成功”的类型。恰巧C++有一个取得这种行为的办法,那就是重载。
为了让advance的行为如我们所愿,我们需要做的就是产生两版重载函数,内含advance的本质内容,但各自接受不同类型的iterator_category对象,我们将这两个函数取名为doAdvance:
template<typename IterT,typename DistT>//这份实现用于随机访问迭代器
void doAdvance(IterT& iter,DistT d,std::random_access_iterator_tag)
{
iter += d;
}
template<typename IterT,typename DistT>//这份实现用于bidirectional迭代器
void doAdvance(IterT& iter,DistT d,std::bidirectional_iterator_tag)
{
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;
}
编译器运用重载解析机制调用适当的实现代码:
template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d)
{
doAdvance(
iter,d,
typename
std::iterator_traits<IterT>::iterator_category()
);
}
注意:
1、特性类(Traits class)使得“类型相关信息”在编译期可用,它们以“模板”和
“模板特化”完成实现。
2、整合重载技术(overloading)后,特性类(Traits class)有可能在编译期对类型执行if...else测试。