说起STL就不能不提到迭代器,它是STL中非常关键的概念,正是它连接了容器和算法,要理解STL思想首先
要理解的便是迭代器,这篇笔记总结了我两次看《STL源码剖析》第三章的阅读笔记,其中也包含了自己的
一些想法,欢迎大家指正。
本文从三方面总结迭代器
- 迭代器的思想
- 迭代器相应型别及traits思想
- __type_traits思想
一 迭代器思想
迭代器的主要思想源于迭代器模式,其定义如下:提供一种方法,使之能够依序巡防某个聚合物(容
器)所含的元素,而又无需暴露该聚合物的内部表达式。可见她的主要作用便是能够降低耦合,提高代码的
模块性。
STL的的中心思想在于:将数据容器和算法分开,彼此独立设计,最后再以一贴胶着剂将它们撮合
在一起,这贴胶着剂便是迭代器。迭代器的行为类似智能指针(例如标准库的auto_ptr和boost库的shared
_ptr),换句话说它重载了* 和 –> 运算符,由于设计一个适用于所有容器的迭代器是非常困难的,每个
迭代器都必须很了解容器,所以STL的每一种容器都提供了相应的专属迭代器。
STL在广义上有5种迭代器类型(不限于这5种,还可以是原生指针等,具体的容器定义自己的迭代器但
是类型是这几种之一或者是原生指针等)
- input iterator : 这种迭代器所指对象不允许外界改变,即是只读的
- output iterator : 唯写
- forward iterator : 允许写入型算法
- bidirectional iterator : 可双向移动的迭代器
- random access iterator : 涵盖所有指针运算能力,可随机访问任何位
它们在STL中的定义如下:
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;
};
二 迭代器相应型别及traits思想
书中把traits技法称为STL源代码的门钥,可见其十分重要。先介绍迭代器相应型别,从字面上意义来
说便是和迭代器相关的类型信息,实际上有5种常用的迭代器类型:
-
value type : 迭代器所指对象的型别
-
difference type : 迭代器之间的距离型别
-
reference type : 迭代器引用型别
-
pointer type : 迭代器指针型别
-
iterator_category : 迭代器本身的型别
STL内部需要知道当前的迭代器的这些型别信息,其所使用的方法主要是模板的参数推导、模板内嵌型
别以及模板偏特化。这里介绍下模板偏特化的概念。
模板的偏特化是指任何template参数更进一步的条件限制所设计出来的一个特化版本,例如
template<typename T>
class C{…} //这个版本允许T为任何类型
template<typename T>
class C<T*>{…} //这个特化版本仅适用于“T为原生指针的”的情况,它比上面的更特殊
有了模板偏特化,就可以让traits萃取出原生指针(譬如vector的迭代器就是原生指针型别的)以及指
向常量的原生指针型别而不仅仅是类类型的,而负责萃取的便是 iterator_traits:
template <class Iterator>struct iterator_traits {
typedef typename Iterator::iterator_category iterator_category;typedef typename Iterator::value_type value_type;typedef typename Iterator::difference_type difference_type;typedef typename Iterator::pointer pointer;typedef typename Iterator::reference reference;};
如果是类类型的性别,用上面这个就可以获得其5个相应型别,当然这些型别必须都在相应iterator类
里面定义好(见第一节的5种迭代器的定义),那么如果不是上面5种而是原生指针等其他型别呢?这时候
就用到了模板偏特化:
//原生指针用这个
template <class T>struct iterator_traits<T*> {
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
};//指向常量的原生指针用这个
template <class T>struct iterator_traits<const T*> {typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef const T* pointer;typedef const T& reference;};
通过这个traits我们就可以获得任何一种iterator的相应型别,通过以下表达式即可:
iterator_traits<…>::…
说到这里有一个很重要是设计思想不得不提,就是通过函数重载在编译时决策正确的函数调用。这个问
题源于5种迭代器的类型,它们的巡防能力是不同的,例如random acess iterator是巡防能力最强的,可以
在O(1)时间巡防指定位置,而这个用其他的迭代器可能需要O(n)。所以为了提高效率,我们应该用和迭代器
类型最匹配的算法函数去调用,能用random access iterator的就不要用其他的。那么怎么做呢?
-
首先通过traits获得iterator_category,这样我们能够知道当前迭代器的类型。实际上iterator_category就是用来提供这种服务的。
-
在函数调用时生成相应迭代器类型的临时对象作为实参传递,编译器就会调用相应的重载函数。
为了重载函数识别,我们有对应的5种迭代器标识类:
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 {};
继承是为了可以使用传递调用,当不存在某种迭代器类型匹配时编译器会依据继承层次向上查找进行传递。
以distance为例:
//这里category()就是为了产生临时对象
template <class InputIterator>inline iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last) {typedef typename iterator_traits<InputIterator>::iterator_category category;return __distance(first, last, category());
}//input iterator 版,注意函数形参最后的类型
template <class InputIterator>inline iterator_traits<InputIterator>::difference_type
__distance(InputIterator first, InputIterator last, input_iterator_tag) {iterator_traits<InputIterator>::difference_type n = 0;while (first != last) {
++first; ++n;}return n;
}//random access iterator 版
template <class RandomAccessIterator>inline iterator_traits<RandomAccessIterator>::difference_type
__distance(RandomAccessIterator first, RandomAccessIterator last,random_access_iterator_tag) {return last - first;
}
三 __type_traits思想
有了前面的基础,我们理解到STL是非常重视效率的,而SGI STL又在其基础上实现了一个
__type_traits,根据前面的经验我们知道它是一个萃取剂,只不过它萃取的型别是:
-
是否具备non-trivial default ctor?
-
是否具备non-trivial copy ctor?
-
是否具备non-trivial assignment operator?
-
是否具备non-trivial dtor?
这里的non-trivial意指非默认的相应函数,我们知道编译器会为每个类构造以上四种默认的函数,如
果没有定义自己的,就会用编译器默认函数,如果使用默认的函数,本来就是按位拷贝我们可以使用memcpy
等函数来加快速度,提高效率。
为了使用函数重载决议,我们使用类类型来定义两种类型,__true_type和__false_type
struct __true_type {
};
struct __false_type {
};
template <class type>
struct __type_traits {
typedef __true_type this_dummy_member_must_be_first;
typedef __false_type has_trivial_default_constructor;
typedef __false_type has_trivial_copy_constructor;
typedef __false_type has_trivial_assignment_operator;
typedef __false_type has_trivial_destructor;
typedef __false_type is_POD_type;
};这个是泛化版,STL对几乎每种内置类型都定义了相应的特化版本来制定它们的类型,整体实现不难。
后记:
通过迭代器的设计,我们能够看到很多非常有价值的思想,也对模板的强大有了更加深刻的认识,这些也是
继续阅读STL源码的基础。