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的内容到底是什么,如果作为类型,那么就可以像普通的结构或者类定义一样,直接使用)
20 外话,背景(traits技巧的背景知识)
Partial Specification(偏特化)
如果class template拥有一个以上的template参数,那么我们可以针对其中template参数进行特化。
提供另一份template定义式,而且它也是templatized
针对任何template参数 更进一步的条件限制所设计出来的一个特化版本
Template<typename T>
Class C{};
Template<typename T>
Class C<T*>{}; //这个特化版本只适用于“T为原生指针”的情况
Int I;
C<&I> c;
//这样就可以使用原生的指针,并且在类中保留指针所指对象的类型,不然,如果template是指针类型的话,还是得不到实际的类型
作者:Aga.J
出处:http://www.cnblogs.com/aga-j
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
个人学习笔记仅供本人记录知识所用,不属发表性文章。