STL主要由”用以表现容器、迭代器和算法“的模板构成;但也覆盖若干工具性模板,比如一个叫做advance的可以用来将某个迭代器移动某个给定距离:
template<typename IterT, typename DistT>
void advence(IterT& iter, DistT d); // d < 0时后移
那应该怎么实现呢?
template<typename IterT, typename DistT>
void advence(IterT& iter, DistT d){
if(iter is random access iterator){
iter += d;
}else{
if(d > = 0) { while(d--) ++iter;}
else {while(d++) --iter;}
}
}
先判断iter是否为随机存取迭代器。也就是说我们需要取得类型的某些信息。这就是traits引入的原因:它们允许你在编译期取得某些类型信息。
Traits并不是C++关键字或者一个预先定义好的构建,它们是一种技术,也是一个c++程序员共同遵守的协议。这个技术的要求之一是,它对内置类型和用户自定义连续性的表现必须一样好。举个例子,如果上面advance收到的实参是一个指针(比如const char *)或者一个int,上面advance还是必须生效。这意味着traits技术也必须能够施行于内置类型比如指针身上。
"traits必须能够施行于内置类型"意味"类型内的嵌套信息"这种东西出局了。因为我们无法将信息嵌套于原始指针内。因此类型的traits信息必须位于类型自身之外。标准技术是把它放进一个模板以及一个或者多个特化模板中。这样的模板在标准库中有很多个,其中针对迭代器的被命名为iterator_traits:
template<typename IterT>
struct iterator_traits; //用来处理迭代器分类的相关信息
如你所见,iterator_traits是个struct。是的,习惯上traits总是被实现为struct,但它们却又往往被称为traits classes。
iterator_traits的运作方式是,针对每一个类型IterT,在struct iterator_traits< IterT>内一定声明某个typedef名为iterator_category。这个typedef用来确认IterT的迭代器分类。iterator_traits两部实现这个方式:
- 首先它要求每一个“用户自定义的迭代器类型”必须嵌套一个typedef,名为iterator_category,用来确认适当的tag struct。比如:
template<...> //略而未写的迭代器参数
class deque{
public:
class iterator{
public:
typedef random_assess_iterator_tag iterator_category;
};
}
template<...> //略而未写的迭代器参数
class list{
public:
class iterator{
public:
typedef bidrectional_iterator_tag iterator_category;
};
}
对于iterator_traits
// IterT::iterator_category用来表现”IterT说它自己是什么“
template<typename IterT>
struct iterator_traits{
typedef typename IterT::iterator_category iterator_category;
};
这对自定义类型行得通,但是对指针(也是一种迭代器)行不通,因为指针不可能嵌套typedef。iterator_traits的第二部分如下,专门用来对付指针
- 为了指针指针迭代器,iterator_traits特别针对指向类型提供一个偏特化版本。由于指针的行径与random assess迭代器类似,所以:
template<typename IterT>
struct iterator_traits<IterT *>{ //针对内置指针偏特化
typedef random_access_iterator_tag iterator_category;
};
总结:如何设计并实现一个traits class
- 确认若干你希望将来可以取得的类型相关信息。比如对迭代器而言,我们希望将来可以取得的其分类(category)
- 为信息选择一个名称(比如iterator_category)
- 提供一个模板和一种特化版本,内含你希望支持的类型相关信息
现在有了iterator_traits,试着实现advance了:
template<typename IterT, typename DistT>
void advence(IterT& iter, DistT d){
if(typeid(typename std::iterator_traits<IterT>::iterator_category == typeid(std::random_access_iterator_tag ))){
}
但是上面代码还是有问题:IterT类型在编译期间获得,所以std::iterator_traits< IterT>::iterator_category 也可以在编译期间确定。但是if语句却在运行期才会确定。
那什么解决呢? 重载:当你重载某个函数f,你就必须详细说明各个重载件的类型。当你调用f,编译期会自动根据传过来的实参选择最适当的重载
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag ){
iter += d;
}
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::bidrectional_iterator_tag ){
if(d > = 0) { while(d--) ++iter;}
else {while(d++) --iter;}
}
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::input_iterator_tag){
if(d < 0)
throw std::out_of_range("Negative distance");
while(d--) ++iter;
}
有了这些重载版本,advance需要做的只是调用它们并额外传递一个对象,后者必须带有适当的迭代器分类。于是编译期运用重载机制解析调用适当的代码:
template<typename IterT, typename DistT>
void advence(IterT& iter, DistT d){
doAdvance(iter, d, typename std::iterator_traits<IterT>::iterator_category());
}
总结:
- traits类使”类型相关信息“在编译期可用。它们以模板和模板特化完成实现
- 整合重载技术后,traits类有可能在编译期对类型执行if-else测试
STL迭代器分类:
- 输入迭代器:只能向前移动,一步一次,客户只可以读取(不能改写)它们所指的东西,而且只能读一次。它们模仿指向输入文件的read pointer。常见例子 istream_iterators
- 输出迭代器:只能向前移动,一步一次,客户只可以改写(不能读取)它们所指的东西,而且只能写一次。它们模仿指向输入文件的write pointer。常见例子 ostream_iterators
输入迭代器和输出迭代器只能向前移动,而且最多只能读/写一次,所以它们只适合”一次性操作算法“
- 前向迭代器:只能向前移动,,可以读写它们所指的东西一次以上。适合”多次性操作算法“
- 双向迭代器:可以前后移动
- 随机访问迭代器:可以随机跳转任意距离
对于这5中分类,C++标准库提供了专属的tag struct加以确认:
struct input_iterator_tag{};
struct output_iterator_tag{};
struct forward_iterator_tag : public input_iterator_tag{};
struct bidrectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidrectional_iterator_tag {};