代码改变世界

STL源码剖析之迭代器概念与traits编程技巧--学习笔记

2011-04-12 10:08  Aga.J  阅读(539)  评论(0编辑  收藏  举报

15 Iterator是一种抽象的设计概念:design pattern中有一种iterator的模式: 提供一种外部方法,使之可以依序访问聚合器所含的各个元素,而又无需暴露该聚合物的内部表述方式(也就是说做到和聚合物的类型无关);

STL的中心思想: 将数据容器和算法分开,彼此独立设计,最后使用粘合剂将他们粘合起来。要做到 容器的泛型化我们可以借助class template,要做到 算法的泛型化,我们可以借助 function template。而他们之间的交合,就需要细致考虑。

下面是三者之间完美结合的例子

Template<class InputIterator, class T>

InputIterator find(InputIterator first, InputIterator last, const T& value)

{

While ( first!=last && *first !=value) //独立于容器,使用迭代器迭代容器中的元素

++first;

Return first;

}

#include<vector>

#include<list>

#include<deque>

#include<algorithm>

#include<iostream>

Using namespace std;

Int main()

{

Const int arraySize=7;

Int ia[arraySize] = {0,1,2,3,4,5,6};

Vector<int> ivect ( ia, ia+arraySize);

List<int> ilist(ia,ia+arraySize);

Deque<int> ideque(ia,ia+arraySize);

Vector<int>::iterator it1 = find( ivect.begin(), ivect.end(), 4);

If(it1 == ivect.end() )

Cout<<”not found”;

Else

Cout<<”found”;

List<int>::iterator it2=find(ilist.begin(),ilist.end(),6);

If( it2== ilist.end())

Cout<<”dd”;

Else cout<<’dd’;

}

16 迭代器其实是一种smart pointer(smart pointer帮助我们封装指针的底层操作,并防止我们因为遗忘指针释放而造成的内存泄露),主要对*和->进行重载,下面是smart pointer的简单实现。

Template<class T>

Class auto_ptr

{

Public :

Explicit auto_ptr(T *p=0):pointee(p){}

Template<class u> Auto_ptr ( auto_ptr<U>& rhs):pointee(rhs.releasse()){}

~auto_ptr(){delete pointee ;}

Template<class U> Auto_ptr<T>& operator = (auto_ptr<U>& rhs){If (this != &rhs) reset(rhs.release());Return *this;}

T& operator*() const{return *pointee ;}

T* operator->() const{return pointee;}

T* get() const { return pointee;}

Private:

T *pointee;

}

17 模仿【16】的smart pointer,我们可以为特定的容器设计一个iterator。

先定义好容器list

Template<typename T>

Class List

{

Void insert_front(T value);

Void insert_end(T value);

Void display ( std::ostream &os = std::cout) const;

Private:

listItem<T>* end;

listItem<T>* front;

long size;

};

Template<typename T>

Class ListItem

{

Public:

T value() const {return value;}

ListItem* next() const{return next;}

Private:

T value;

ListItem* next;

};

Template<class Item>

Struct ListIter

{

Item* ptr;

ListIter( Item*p =0):ptr(p){} //传入的是具体的item,操作的是iterator

Item& operator*() const{return *ptr;} //迭代器所必须要的操作

Item* operator-> const{return ptr;}

ListIter& operator++(){ptr = ptr->next(); return *this;} //返回下一个迭代子

ListIter operator++(int) { ListIter tmp=*this; ++*this;return tmp;}

Bool operator==(const ListIter& i) const{return ptr==i.ptr;} //迭代子比较

Bool operator != (const ListIter& i) const{return ptr!=i.ptr;}

};

//最后是使用这个”迭代子”和”容器”和”算法”

Void main()

{

List<int> mylist;

For(int i=0;i<5;++i)

{

Mylist.insert_front(i);

Mylist.insert_end(i+2);

}

Mylist.display();

ListIter<ListItem<int> > begin(mylist.front());

ListIter<ListItem<int> > end;

ListIter<ListItem<int> > iter;

Iter= find(begin,end,3);

}

前面的find的定义为

Template<class InputIterator, class T>

InputIterator find(InputIterator first, InputIterator last, const T& value)

{

While ( first!=last && *first !=value) //独立于容器,使用迭代器迭代容器中的元素

++first;

Return first;

}

在这种情况下,无法直接将 *first和value进行比较,所以我们只能重载operator!=函数

Bool operator!=(const ListItem<T>& item,T n)

上面整个过程演示了 容器,容器项,容器项迭代子,算法的结合过程,首先定义容器项,每个容器项除了知道自身的信息外,还知道其邻居的地址,而容器项是容器的基础,所以定义一个容器,其中包含对容器的基本操作,借助容器项member来实现。最后定义一个迭代子,重载必要的运算符,接收容器项的特定模板类型,完成对应容器项的迭代子类型,并传入泛型模板函数中,进行算法计算。

看起来是一个不错的设计,但是其实我们暴露了很多关于list的细节!!!!

1 为了得到begin和end这两个迭代器,我们将ListItem作为模板参数传入迭代子的构造过程中,结果是暴露了list中的listItem

2 在listIter中,为了完成++,我们暴露了ListItem的next函数,这样一来,我们就知道每个ListIter对应的item都必须有next函数。所以为了要设计迭代器,我们就必须对listItem和list的实现细节很了解。

所以STL最终选择将迭代器的实现交由容器的实现者来完成,并封住好细节

18我们在算法中使用iterator的时候,可能会用到这个iterator所相应的类型,但是我们不知道迭代器所指的对象的类型。

我们可以借助模板参数,以下提供这个巧妙的方法来获取【在使用到迭代器的函数中使用迭代器的模板参数的具体类型】

过去的函数是这样:

Template<class I>

Void func_impl(I iter) //传入的是一个迭代器比如vector<int>

{

//但是在函数里面我们只知道vector<int>,而不知道该vector是int型迭代器

}

所以用下面的方法来解决:

Template<class I,class T>

Void func_impl(I iter, T t)

{

T tmp; //T用来指明迭代器模板参数类型

}

Template<class I>

Inline void func(I iter)

{

Func_impl(iter,*iter);

}

Int main()

{

Int I;

Func(&i);

}

19

迭代器所指对象的类型,称为迭代器的value type

Template<class T>

Struct MyIter

{

Typedef T value_type;

T *ptr;

MyIter(T *p=0) : ptr(p){}

T& operator*() const {return *ptr;}

};

Template<class I>

Typename I::value_type func(I ite){return *ite;}

//..

MyIter<int> ite(new int(8));

Cout<<func(ite);

//可以看到func要的是MyIter<int>,而返回的希望是<int>,所以在MyIter定义的时候,保留<T>的类型,这样就可以顺利返回了。

//注意func函数的返回类型前面要加上typename来指明MyIter<T>::value_type代表的是一个类型。(不然在模板类在被编译器具现化前,编译器无法判断)

clip_image002

20

Partial Specification(偏特化)

如果class template拥有一个以上的template参数,那么我们可以针对其中template参数进行特化。

提供另一份template定义式,而且它也是templatized

针对任何template参数 更进一步的条件限制所设计出来的一个特化版本

Template<typename T>

Class C{};

Template<typename T>

Class C<T*>{}; //这个特化版本只适用于“T为原生指针”的情况

clip_image004

Int I;

C<&I> c;

//这样就可以使用原生的指针,并且在类中保留指针所指对象的类型

21

Traits方法:可以用来萃取迭代器的特性

Template<class I>

Struct iterator_traits //输入带有value_type的指针

{

Typedef typename I::value_type value_type;

}

Template<class I>

Typename Iterator_traits<I>::value_type

Func(I ite)

{

Return *ite;

}

//这样做好像仅仅在原来的I上面封装了一个Iterator_traits,但是我们可以使用特化版

Template<class T>

Struct iterator_traits<T*> //可以输入原生指针

{

Typedef T value_type;

}

clip_image006clip_image008

为了让这个traits可以运作,每个迭代器都必须自己定义 内嵌型别 nest typedef!!!!

clip_image010

22

如果希望容器能很好的与预定义的STL交互,那么就要为 自定义的容器 的迭代器 定义下面5个类型。

Template<class I>

Struct iterator_traits

{

Typedef typename I::iterator_category;

Typedef typename I::value_type; //获取迭代器所指的对象的类型

Typedef typename I::difference_type; //迭代器间距,容器的容量

Typedef typename I::pointer;

Typedef typename I::reference; //返回引用类型

};

Template <class I,class T>

Typename iterator_traits<I>::difference_type count(I first, I last, const T& value)

{

Typename iterator_traits<I>::difference_type n=0;

For(;first!=last;++first)

If(*first ==value)

++n;

Return n;

}

//偏特化,得到原生指针的difference type

Template<class I>

Struct iterator_traits<T*> //<const T*>

{

Typedef ptrdiff_t difference_type;

}

23

迭代器可以分为以下几大类:

1 Input Iter

2 Output Iter

3 Forward Iter

4 Bidirectional Iter

5 Random Access Iter

我们一般会根据不同的iter采取不同的行为

Template<class InputIterator, class Distance>

Void advance(InputIterator& I, Distance n)

{

If( is_random_access_iterator(i);

Advance_RAI(I,n);

Else if(xxx)

Advance_xx(I,n);

//…..

}

这种做法只能在执行期才确定使用哪个版本(if - else ),会影响程序的效率,最好可以在编译时就选择正确的版本。

我们可以通过一个模板的形式,并利用traits萃取出迭代器的种类,利用这个迭代器相应型别作为advance的第三个参数。

【什么是traits?

Template<class I>

Struct iterator_traits

{

Typedef typename I::value_type value_type;

};

Trait的作用就是如果模板参数类I有自己的value type,那么通过traits,可以萃取出的value type就是I的value type,这样一来,我们在使用一个traits就可以得到这个模板参数I的value type

Template<class I>

Typename iterator_traits<I>::value_type func(I ite)

{

Return *ite; //*操作会被重载

}

利用traits技法,可以在iterator上封装多一个iterator_trait,将类型信息作为其数据成员,这样一来就可以方便的获取一个模板迭代器的实际类型了】

我们可以通过一个模板的形式,并利用traits萃取出迭代器的种类,利用这个迭代器相应型别作为advance的第三个参数。

首先定义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{};

接下来就可以为advanced函数定义重载函数,每个函数都有一个相应上面类型的参数。

Template<class input_iterator_tag, class distance>

Inline void advance(input_iterator &I, distance n, input_iterator_tag)

{

While (n--) ++i;

}

Template<class RandomAccessIterator, class Distance>

Inline void advance(RandomAccessIterator& I, distance n, random_access_iterator_tag){};

上面是两个重载的函数,和以前不同的是加入了tag类型

接下来就是一个接口来在编译期判定执行那个函数

Template<class InputIterator, class distance>

Inline void advance(InputIterator& I, distance n)

{

Advance(I, n, iterator_traits<inputIterator>::iterator_category() );

//精华就在这里,通过traits的技法,将InputIterator的实际相关信息萃取出来,作为参数处理,这样可以很好的利用上重载的好处。而且 traits还支持偏特化性质。

}

24 如果模板中有一个模板参数是基类,然后没有一个模板参数是子类的重载,那么使用子类传入时,会调用基类的模板

Template<class InputIterator>

Inline iterator_traits<InputIterator>::difference_type __distance(InputIterator first, InputIterator last, input_iterator_tag){}

Template<class RandomAccessIterator>

Inline iterator_traits<RandomAccessIterator>::difference_type __distance(RandomAI first, RandomAI last, random_access_iterator_tag){}

Template<class InputIterator>

Inline iterator_traits<InputIterator>::difference_type distance(InputIterator first, InputIterator last)

{

Typedef typename iterator_traits<InputIterator>::iterator_category;

Return __distance(first, last, category() );

}

这个distance可以接收任何类型的迭代器,这里的inpputIterator是按照STL算法的命名规则,按所能接受的最初级别来命名。而如果这些迭代器有继承关系,那么在使用Out,For,Bid这3个迭代器的话会默认调用input那个类型的迭代器,这和一开始的【如果模板中有一个模板参数是基类,然后没有一个模板参数是子类的重载,那么使用子类传入时,会调用基类的模板】的说法一致

关于能提供traits的迭代器,STL提供了一个基类,让我们实现自己的迭代器。

25 【24】

迭代器 需要 设计适当的 associated type

容器 需要 设计适当的 迭代器

(因为只有容器才能知道如果利用迭代器来遍历自己,并执行迭代器所应该有的各种行为-如前进等)

算法 可以独立于 容器 和 迭代器 ,以迭代器为对外接口就可以

Traits技巧! 利用“内嵌类型和template参数推导(Typedef typename I::value_type value_type;)”使得C++可以执行类型识别(因为C++本身不是一门强类型的语言)

26

STL仅仅使用traits对迭代器加以规范,制定出iterator_traits,而SGI则拓展了它,实现了__type_traits 用来萃取类型(type)的特性,如果我们可以获得某个类型的特性,例如 是有由non-trivial default constructor等等,那么我们就可以在构造,析构等操作时,选择最有效率的措施,而采用直接操作提供效率。

27

clip_image012 clip_image014