引言
- 想必自己实现过C数据结构的同学,都深有体会,如果底层使用线性表实现存储,则遍历可以直接通过索引查询数据,但是如果通过链表实现存储,则遍历要通过类似
next()
指针不断的移动来比例不同的节点。 - STL容器底层存储不一,但是遍历却是那么的统一。不知使用STL容器进行遍历的时候,是否想过,为什么呢?怎么搞的?
概念
- 迭代器是一种抽象的设计概念,iterator模式定义如下:
提供一种方法,使之能够依序访问某个聚合物所含的各个元素,从而无需暴露该聚合物的内部表述方式 - 使用STL能够使泛型化的容器和算法撮合在一起
- 迭代器是一种类似于指针的存在,迭代器最重要就是对
operator*
和operator->
进行重载。直接看下面的用法例子
int a[] = {1, 2, 3, 4};
vector<int> v(a, a + sizeof(a) / sizeof(int));
//使用迭代器遍历
vector<int>::iterator t = v.begin();
for(; t != v.end(); ++t) {
cout << *t << endl;
}
- 可以通过上面的例子看到,通过t我们可以直接像使用指针一样遍历vector,而不需要关注vector的实现。毕竟对链表的访问和数组的访问是不同的,但是我们都可以通过
++t
去递增。
初探iterator
- 这一节将会通过一个小的daemon(链表)来理解迭代器
//链表节点
template <typename T>
class ListItem {
public:
ListItem* next();
private:
T m_value;
ListItem* m_next;
};
//链表
template <typename T>
class List {
public:
void insert_front(T value);
private:
ListItem<T>* m_end;
ListItem<T>* m_front;
};
//迭代器
template <class Item>
struct ListIter {
Item *m_ptr;
//前置++
ListIter& operator++() {
m_ptr = m_ptr->next();
return *this;
}
//后置++
ListIter operator++(int) {
ListIter tmp = *this;
++*this;
return tmp;
}
Item& operator*() const {
return *m_ptr;
}
bool operator !=(const ListIter& i) const {
return m_ptr != i.m_ptr;
}
};
void daemon() {
List<int> my_list;
for(int i=0; i<5; ++i)
my_list.insert_front(i);
ListIter<ListItem<int>> t = my_list.begin();
for (; t != my_list.end(); ++t) {
cout << *t << endl;
}
}
- 从上面的例子我们可以看到:
- 迭代器的实现有针对性,比如此处的ListIter只能针对list,如果想针对vector,那内部许多函数又需要重新设计实现。
- 要实现List的迭代器,我们无可避免的暴露了太多List的实现细节,比如:
ListItem
,next()
。如果不是为了迭代器,这些本可以不用暴露的。
深入iterator
- 上面章节通过一个简单的例子,提供了一个迭代器的雏形。但是距离STL的迭代器还相差很多,下面我们继续深入
traits
- 在迭代器使用的时候,我们可能会想要知道迭代器所指对象的型别, 比如:所指对象的类型
- 一种解决拌饭是使用function template的参数推导机制,如下:
int main() {
int i;
func(&i);
}
//此处I 为 int*
template<class I>
void func(I iter) {
func1(iter, *iter);
}
template<clas I, class T>
void func1(I iter, T t) {
T tmp; //T就是迭代器所指之物的型别,此处为int
}
- 根据经验,一般这种
型别
有五种,并不是所有的都能通过上面这种方法解决。我们需要更加通用的解决办法,正好,通过声明内嵌型别可以完成,如下:
template <class T>
struct MyIter {
typedef T value_type; //内嵌型别
T *m_ptr;
};
template <class I>
//此处typename用于高速编译器这是一个型别,不能删除
typename I::value_type func(I ite) {
return *ite;
}
- 但是对于所有的迭代器都是 class type, 原生指针就不是。但是STL又必须接受原生指针作为迭代器。解决办法就是偏特化,就是为每种原生指针提供一个特化版本。如下:
template <typename T>
class C<T*> {
};
- 如果我们想要使用迭代器的型别,肯定不能直接通过迭代器来获取,因为无法处理原生指针的问题,因此,我们需要一个工具,可以提供统一的接口,来获取所有迭代器的型别。这既是
traits
,也叫萃取器,如下:
//I为迭代器,是class type类型
template <class I>
struct iterator_traits {
//迭代器类型
typedef typename I::iterator_category iterator_category;
//迭代器所指对象的型别
typedef typename I::value_type value_type;
//两个迭代器之间的距离,可以表示一个容器的最大容量
typedef typename I::difference_type difference_type;
//左值传递,地址
typedef typename I::pointer pointer;
//左值传递,引用
typedef typename I::reference reference;
};
//针对原生指针,需要进行偏特化
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;
typdef T& reference;
};
//针对原生const指针
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;
typdef const T& reference;
};
分类
- 根据移动特性与实施操作,迭代器可以分为五类:
- input iterator
- 只读,迭代器所指对象不允许外界改变。支持
opeator ++
- 只写,支持
opeator ++
- 读写,允许‘写入型’算法,通过迭代器读写.支持
opeator ++
- 可双向移动,某些算法可能需要逆向访问迭代器。支持
opeator ++
,operator--
- 支持多种运算,包括p+n, p-n, p[n], p1-p2, p1<p2
- 五种迭代器分类,依次强化
- 设计算法时,我们尽量针对某种迭代器提供一个明确的定义,这样才能在不同的情况下提供最大的效率。将入有个算法可以接受 forward iterator, 使用random access iterator当然也可以,但是并不是最佳的。
- 下面的例子可以加深我们的理解
//逐步前进
template <class InputIterator, class Distance>
void advance_1(InputIterator& i, Distance n) {
while (n--)
++i;
}
//双向前进
template <class BidrectionalIterator, class Distance>
void advance_2(BidrectionalIterator& i, Distance n) {
if (n >= 0)
for (; n>0; --n, ++i);
else
for (; n<0; ++n, --i);
}
//跳跃前进
template <class RandomAccessIterator, class Distance>
void advance_3(RandomAccessIterator& i, Distance n) {
i += n;
}
- 上面的例子可以看出,对于RandomAccess Iterator而言,使用advance_1,原本O(1)编程了O(n)。因此我们需要根据迭代器的类型,来选择不同的版本,这样可以提高效率如下:
template <class InputIterator, class Distance>
void advance(InputIterator& i, Distance n) {
if (is_random_access_iterator(i))
advance_3(i, n);
else if (is_bidirectional_iteator(i))
advance_2(i, n);
else
advance_1(i, n);
}
- 如果执行期间才决定使用哪个版本,会影响效率,最好是在编译期就选择正确的版本,刚好,重载函数机制可以办到。相应的我们需要修改advance函数集,添加iterator型别,如果再配合traits就更加完美。改进后的算法如下:
//五种类型iterator的定义,只用于标记类型,因此不需要任何成员
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 {};
//逐步前进
template <class InputIterator, class Distance>
void _advance(InputIterator& i, Distance n, input_iterator_tag) {
while (n--)
++i;
}
template <class InputIterator, class Distance>
void _advance(InputIterator& i, Distance n, forward_iterator_tag) {
_advance(i, n, input_iterator_tag());
}
//双向前进
template <class BidrectionalIterator, class Distance>
void _advance(BidrectionalIterator& i, Distance n, bidirectional_iterator_tag) {
if (n >= 0)
for (; n>0; --n, ++i);
else
for (; n<0; ++n, --i);
}
//跳跃前进
template <class RandomAccessIterator, class Distance>
void _advance(RandomAccessIterator& i, Distance n, random_access_iterator_tag) {
i += n;
}
template <class InputIterator, class Distance>
void advance(InputIterator& i, Distance n) {
//iterator_traits<InputIterator>::iterator_category()会产生一个临时的对象
_advance(i, n, iterator_traits<InputIterator>::iterator_category());
}
template <class I>
inline typename iterator_traits<I>::iterator_category iterator_category(const I&) {
typedef typename iterator_traits<I>::iterator_category category;
return category();
}
规范
- STL规范要求,任何迭代器都应该提供五个内嵌型别,以便traits萃取。STL提供了一个iterators class,如下,新设计的迭代器继承它,就能保证符合STL规范。
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;
};
- iterator不包含任何成员,只是型别定义,所以继承它不会有任何负担。使用如下:
template <class Item>
struct ListIter : public std::iterator<std::forward_iterator_tag, Item> {};
- 总结:
- 设计适当的型别是迭代器的责任。
- 设计适当的迭代器是容器的责任。因为只有容器才知道怎么遍历自己。
SGI __type_traits
- STL只对迭代器指定了iterator_traits, 而SGI把这种技术扩大到了迭代器以外,也就是所谓的
__type_traits
- 此处的型别指
- non-trivial defalt ctor? 构造
- no-trivial copy ctor? 拷贝
- non0-trivial assignment operator? 赋值
- non-trivial dtor? 析构
__type_traits<T>::has_trivial_default_constructor
__type_traits<T>::has_trivial_copy_constructor
__type_traits<T>::has_trivial_assignment_operator
__type_traits<T>::has_trivial_destructor
__type_traits<T>::is_POD_type
//以上只需要知道 '真' 或 '假'
struct __true_type {};
struct __flase_type {};
- 有了以上这些__type_traits定义如下:
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_assignment_operator;
typedef __false_type has_trivial_destructor;
typedef __false_type is_POD_type;
};
- SGI 将所有值定义为__false_type,是因为定义最保守的值 。然后通过定义特化版本来优化比如
__STL_TEMPLATE_NULL struct __type_traits<unsigned int> {
/*它通知有能力__type_traits特化的编译器,当前__type_traits是特殊的。为了保证编译器使用一个同名的但功能与此无关联的template时,所有的时期都能够顺利运行。
*/
typedef __true_type this_dummy_member_must_be_first;
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
- 某些编译器会自动为所有型别提供偏特化版本。
- 在一些算法中会通过使用__type_traits来提高效率,因为可以跳过没有必要的拷贝,构造,析构,赋值
- 如果我们自己定义了一个类,我们可以通过定义它的偏特化版本,告诉编译器,在使用一些算法时使用效率高的版本。否则将全部默认为__type_false
完结
- 关于迭代器的初步了解就在这里。更深的了解会在终极篇中和大家分享
- 最后向大家推荐 《STL源码剖析这本书》,非常适合STL迭代器原理的入门学习。