本文的技术可以参考本博客: Traits 技术 --- 模板元编程 (转)
迭代器可以区分为不同的类型,每个类型都有特定的迭代器功能。
根据迭代器类型,将算法根据类型的不同实现出更加有效率的版本,将会很有用,也很必要!
透过迭代器标志(tags)和迭代器特性(traits,由<iterator>提供)可以实现这样的重载.
STL为每种迭代器都提供了一个迭代器标志(iterator tags),用来作为迭代器的标签(label)
namespace
{
struct output_iterator_tag
{
};
struct input_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
{
};
}
-------------------------------------------------------------------------------------------
五种迭代器类别
首先,引进迭代器是为了在算法与容器之间隔开,可以让两者独立发展。这样势必要求迭代器需要像算法展现统一的接口,也就是说,从算法的角度出发,迭代器的功能接口是一样的,算法无法直接看到容器的,而是通过迭代器简介管理操作容器,这样可以推到出:算法眼里容器都是一样的。而显然这并不合理。
因为,即使各种容器有统一的基本特性——“聚集”同一类型的数据元素。但是由于不同的容器聚集的方式不同,导致表现出来的许多功能特性不同。(例如vector,deque表现可以很好的支持random access性质,但是你叫list也支持这个,这将是非常不明智的)。所以,即使我们尽量希望向外部展现一个统一的容器接口(迭代器),我们仍然需要区分对待,我们在迭代器上区分对待,就可以充分发挥各种容器的个性特点。算法也可以根据相应的不同,优化对待不同的容器。这就是出现不同的类型容器的原因。
Iterator Category | Ability | Providers |
Input iterator | Reads forward | istream |
Output iterator | Writes forward | ostream, inserter |
Forward iterator | Reads and writes forward | |
Bidirectional iterator | Reads and writes forward and backward | list, set, multiset, map, multimap |
Random access iterator | Reads and writes with random access | vector, deque string, array |
可以看到,不同类别的迭代器,所能支持的功能是不同的,定义容器的选择定义哪个类别的应不同的容器特性而定。在设计算法的时候,算法会通过类型萃取获得该迭代器的类别。并根据不同的类别视机做特定的算法实现优化。
Input iterator
Operations of Input Iterators |
Expression | Effect |
*iter | Provides read access to the actual element |
iter ->member | Provides read access to a member (if any) of the actual element |
++iter | Steps forward (returns new position) |
iter++ | Steps forward (returns old position) |
Iter1 == iter2 | Returns whether two iterators are equal |
Iter1 != iter2 | Returns whether two iterators are not equal |
TYPE(iter) | Copies iterator (copy constructor) |
这个迭代器用于,从容器中依次读取数据(只读,并且只能前进)。但是还有一个会让你匪夷所思的特性是,这种迭代器不能重复读某个元素两次。这是一个很奇怪但是有很合理的规定,最常见的需要用输入迭代器的就是标准输入口,不管有几个迭代器指向这个标准输入口,语义上不容许同一个元素被读两次。但是,我们奇怪的是,这个意思的就是说,读动作是与迭代器相前进一步的动作是一起发生的,那么这种迭代器就不需要向前进一步的功能的必要了,但这里提供了。
事实上我认为,上面的要求只是一种语义上的要求。告诉你,如果你在某个容器定义迭代器的时候选择了输入迭代器,那么这个迭代器所应该提供的功能模式应该需要保持“不容许同一个元素被读两次”,我们知道STL对各种类别的区别只是用tags来区别的。语法上没有写死语义上的“建议”。
还有一个原因是,下面有几种迭代器,是以这种迭代器为基础的,所以如果你不提供这种基本的操作(向前去!),那他们如何提供。
Output iterator
Operations of Output Iterators |
Expression | Effect |
*iter = value | Writes value to where the iterator refers |
++iter | Steps forward (returns new position) |
iter++ | Steps forward (returns old position) |
TYPE (iter) | Copies iterator (copy constructor) |
与input iterator 想对应的就是output iterator,他们有很多相似点,并且,也有与前面类似的奇怪规定,这导致两个指向同一个容器的写迭代器,写的过程不会出现覆盖写。其中一个有名的例子就是屏幕输出。并且,你会发现写迭代器没有比较操作(读模式中就有)。这是因为写的时候,是一直往外写的,语义上以没有终点限制的。有一个特殊的(也是我们将要介绍的)迭代器,就是inserter。
Forward iterator
Operations of Forward Iterators |
Expression | Effect |
*iter | Provides access to the actual element |
iter-> member | Provides access to a member of the actual element |
++iter | Steps forward (returns new position) |
iter++ | Steps forward (returns old position) |
iter1 == iter2 | Returns whether two iterators are equal |
iter1 != iter2 | Returns whether two iterators are not equal |
TYPE() | Creates iterator (default constructor) |
TYPE(iter) | Copies iterator (copy constructor) |
iter1 = iter2 | Assigns an iterator |
从iterator中表达的迭代器tags定义可以看出:
struct forward_iterator_tag : public input_iterator_tag {};
forward iterator 是以input iterator为基础的。这就是给我们一个奇怪的错觉。语义上forward只限制了向前,不限制读写,这样forward iterator应该继承input iterator和output iterator两种。但是它只支持一种。
这里我们提出两个原因:
首先,iterator中迭代器tags的规定只是语义上的规定,它对最终的具体实现没有什么限制,(当然你不按照这种方式实现,那将是一个不道德的行为)。所以这种定义,从本质实现上并不能影响我们许多。
其次,权威的书籍中所阐述的原因是,output中,由于语义上不提供比较功能(这就表明不判断是否越界)。所以forward不能全部继承output的语义功能。
最后,我说一句,这里的规定始终都是语义上的规定,所以基本上,forward iterator继承谁,都是一句屁话。这是标准,但是对于实际实现的程序员来说,这些东西都是屁。
Bidirectional iterator
Additional Operations of Bidirectional Iterators |
Expression | Effect |
-- iter | Steps backward (returns new position) |
iter-- | Steps backward (returns old position) |
可以看出,双向迭代器只是在forward iterator上面加上一个向后一步的功能。我们事实上我们知道我们通常见到的迭代器都这种,以及后面一种Random iterator.
Random iterator
Additional Operations of Random Access Iterators |
Expression | Effect |
iter[n] | Provides access to the element that has index n |
iter+=n | Steps n elements forward (or backward, if n is negative) |
iter-=n | Steps n elements backward (or forward, if n is negative) |
iter+n | Returns the iterator of the nth next element |
n+iter | Returns the iterator of the nth next element |
iter-n | Returns the iterator of the nth previous element |
iter1-iter2 | Returns the distance between iter1 and iter2 |
iter1<iter2 | Returns whether iter1 is before iter2 |
iter1>iter2 | Returns whether iter1 is after iter2 |
iter1<=iter2 | Returns whether iter1 is not after iter2 |
iter1>=iter2 | Returns whether iter1 is not before iter2 |
可以看出,Random iterator的功能主要体现在,在forward iterator 的基础上提供了随机存储的功能,这个功能连带提供了迭代器算数功能(指针算数功能)以及比较功能。
-------------------------------------------------------------------------------------------
类型萃取器问题前面提到,类型萃取器用于给算法使用,同过它你可以得到有关你所操纵的迭代器的几乎所有有用的相关类型。
namespace std {
template <class T>
struct iterator_traits {
typedef typename T::value_type value_type;
typedef typename T::difference_type difference_type;
typedef typename T::iterator_category iterator_category;
typedef typename T::pointer pointer;
typedef typename T::reference reference;
};
}
算法可以使用如下语句得到相关类型:
typename std::iterator_traits<T>::value_type
有些书上说,这种设置有两种好处:
首先,它规定了每种迭代器在定义的时候都需要提供者几种类型信息以供被使用,从某种角度上讲,它提供一种定义迭代器有关相关属性的标准。
为了强行对这一标准进行规定,在iterator头文件中有一个结构(struct iterator)用于给用户定义迭代器的时候继承的(就是接口),但是这并非是语法规定。事实上,如果你如果能够保证一定会提供traits结构中所需求的那些类型信息,不继承这个也是可以的:
template <class Category, class T, class Distance = ptrdiff_t,
class Pointer = T*, class Reference = T&>
struct iterator {
typedef Category iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef Pointer pointer;
typedef Reference reference;
};
事实上,对于我们系统自定义的那五种迭代器,我们使用的比较多的仍然是tags。但是系统为了用户子定义迭代器方便,仍然按照各自的特点,给他们各自定义了类似于iterator的、用于管理traits所指明的类型信息。
例如,定义特定类别迭代器的时候继承特定结构体,你就可以不同管trait规定的那些东西了。
template <class T, class Distance> struct input_iterator {
typedef input_iterator_tag iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef T* pointer;
typedef T& reference;
};
struct output_iterator {
typedef output_iterator_tag iterator_category;
typedef void value_type;
typedef void difference_type;
typedef void pointer;
typedef void reference;
};
template <class T, class Distance> struct forward_iterator {
typedef forward_iterator_tag iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef T* pointer;
typedef T& reference;
};
template <class T, class Distance> struct bidirectional_iterator {
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef T* pointer;
typedef T& reference;
};
template <class T, class Distance> struct random_access_iterator {
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef T* pointer;
typedef T& reference;
};
其次,它让普通指针也被列为迭代器的一种。
namespace std {
template <class T>
struct iterator_traits<T*> {
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef random_access_iterator_tag iterator_category;
typedef T* pointer;
typedef T& reference;
};
}
如上面所说,通过模板偏特化技术,使得当算法获得了普通指针作为迭代器的时候,需要同样知道那些类型(事实上,这种情况下获得还是比较简单的,但是需要和普通迭代器进行统一,以实现泛型
为什么不使用常变量呢?指针没办法!所以traits技术最大的亮点就在于可以对traits类进行特化。