代码改变世界

STL源码剖析之迭代器的概念和traits编程的技法(上)

2011-06-05 10:06  Aga.J  阅读(1036)  评论(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);         //算法find适用于各种容器

If( it2== ilist.end())

Cout<<”dd”;

Else cout<<’dd’;

}

 

16 迭代器其实是一种smart pointer(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;

}

 

模仿上面的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进行比较,所以我们只能在ListIter中重载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 针对[18],STL的做法是使用trait技巧

迭代器所指对象的类型,称为迭代器的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代表的是一个类型。(不然在模板类在被编译器具现化前,编译器无法判断class I 内的value_type的内容到底是什么,如果作为类型,那么就可以像普通的结构或者类定义一样,直接使用

 clip_image002

 

20 外话,背景(traits技巧的背景知识

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;

//这样就可以使用原生的指针,并且在类中保留指针所指对象的类型,不然,如果template是指针类型的话,还是得不到实际的类型