迭代器概念与traits技法
3.1 迭代器设计思维
STL的中心思想在于:将数据容器和算法分开。而迭代器就是将数据容器与算法连接起来。
以find()函数为例:
template<class InputIterator,class T>
InputIterator find(InputIterator first,
InputIterator last,
const T& value){
while(first!=last && *first!=value)
++first;
return first;
}
int main()
{
vector<int> ivec{0,1,2,3,4,5,6};
vector<int>::iterator it1=find(ivec.begin(),ivec.end(),4);
if(it1 == ivec.end())
cout<<"4 not found."<<endl;
else
cout<<"4 found."<<*it1<<endl;
}
//事实上,只要给予不同迭代器,find()便能够对不同容器进行查找操作。
3.2 迭代器是一种smart pointer
迭代器的功能与指针相类似,指针最常见的功能是deference
和member access
,因此迭代器的工作就是对operator*
和operator->
进行重载。
我们尝试为list
设计一个迭代器:
template<class Item>
struct ListIter
{
Item* ptr;
ListItem(Item* p=0)
:ptr(p){ }
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)
{
return ptr != i.ptr;
}
};
//现在将list和find()用ListIter联合起来
int main()
{
List<int> mylist;
for(auto i:{1,2,3,4,5}){
mylist.insert_front(i);
mylist.insert_end(i+2);
}
mylist.display();
//ListItem为单向链表
ListIter<ListItem<int>> begin(mylist.front());
ListIter<ListItem<int>> end;
ListIter<ListItem<int>> iter;
iter=find(begin,end,3);
if(iter == end)
cout<<" not found."<<endl;
else
cout<<" found."<<iter->value()<<endl;
}
由于find()
函数内以*iter!=value
来检查元素值是否吻合,但是iter
的类型为ListItem<int>
,所以要写一个全局operator!=
重载函数。
template<typename T>
bool operator!=(const ListItem<T>& item,T n){
return item.value()!=n;
}
3.3迭代器相应型别
如果在运用迭代器的时候,我们需要使用迭代器所指对象的类型(相应型别之一),我们应该怎么做?
* 利用function template
的参数推导机制。
template <class I,class T>
void func_impl(I iter,T t)
{
T tmp;//T就是迭代器所指之物的类型
...
};
template <class I>
void func(I iter)
{
func_impl(iter,*iter);
}
int main()
{
int i;
func(&i);
}
我们以func()
作为对外接口,却把实际操作置于func_impl()
之中。func_impl()
是一个function template
,一旦被调用,编译器会自动进行自动template
参数推导。于是导出型别T,顺利解决问题。
但是,迭代器相应型别不只是“迭代器所指对象的类型”一种,根据经验,常见的相应型别有五种,然而并非任何情况下任何一种都可以利用上述的template
参数推导机制来取得。
3.4 Traits编程技法——STL源代码门匙
迭代器所指对象的类型,称为该迭代器的value type
。上述的技巧可以用于value type
,但是如果value type
用于函数的传回值,那么上述方法就不管用了,毕竟template
参数推导机制只能推导参数,无法推断函数的返回值类型。那么该怎么做?
声明内嵌类型是个不错的主意:
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; }
T为一个template参数,在它被编译器具现化之前,编译器不知道MyIter<T>::value_type
为一个类型或者member function
或者data member
,typename
是告诉编译器这是一个类型。
这只是对于class type
的迭代器,但是对于原生指针就不行了,所以STL将它们进行了偏特化(template partial specialization)
。
假设有一个class template
如下:
template <typename T>
class C{ ... };
那么其偏特化为:
template <typename T>
class C<T*> { ... };
//这个特化版本仅适用于"T为原生指针"的情况
//"T 为原生指针便是”T为任何类型"的更进一步的条件限制
现在,我们可以针对“迭代器之template
参数为指针”者,设计特化版的迭代器。
下面的class template
来萃取迭代器的特性,value type
为特性之一:
template <class I>
struct iterator_traits{ //traits为“特性”
typedef typename I::value_type value_type;
};
对于原生指针,则设计一个偏特化版本如下:
template<typename T>
struct iterator_traits<T*>{
typedef T value_type;
};
对于iterator_traits<const int*>::value_type
,我们通过上面的特化版本获得的是const int
而不是int
,这不是我们想要的结果。
对此,我们也可以对其设计一个特化版本:
template<typename T>
struct iterator_traits<const T*>{
typedef T value_type; //萃取出来的就是 T 而不是 const T
};
traits
就像是一台“特性萃取机”,榨取各个迭代器的特性。
最常用的迭代器相应类型有:
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;
};
3.4.2 difference_type
difference_type
用来表示两个迭代器之间的距离,因此可以用来表示容器的最大容量。
如果一个泛型算法提供计数功能,则传回值必须使用difference_type
。
//例如count()
template<class T,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
也有对原生指针的两个特化版本。
3.4.3 reference type
在C++中,函数要传回左值,都是以by reference
的方式进行。
3.4.4 pointer type
传回一个左值,令它代表p所指之物的地址。
在ListIter class
中,我们可以看到
Item& operator*() const { return *ptr; }
Item* operator&() const { return ptr; }
Item&
为ListIter
的reference type
,而Item*
便是pointer type
。
reference type
和pointer type
有对原生指针的两个特化版本。
3.4.5 iterator_category
根据移动特性与施行操作,迭代器被分为5
类:
* input iterator:这种迭代器所指的对象,不允许外界改变,只读。
* output iterator:只写。
* forward iterator:允许“写入型”算法在此迭代器所形成的的区间上进行读写操作。
* bidirectional iterator:可双向移动。某些算法需要逆行走访某个迭代器区间,可以使用bidirectional iterators。
* random access iterator:前四种迭代器只供应某一部分指针算术能力,第五种则涵盖所有指针算术能力。
这五种迭代器是包含关系,但是在重载时,这五种迭代器具有继承关系。
iteraor_category
迭代器的目的就是萃取迭代器类型。
例如advance()
函数中iterator_category
将迭代器类型萃取出来,并根据相应型别,调用不同_advance()
,使得效率最大。
iterator_category
也具有原型指针的偏特化版本,但是两者都属于random access iterator
。
任何迭代器,类型永远落在“该迭代器所隶属之各种类型中,最强化的一个”。
对于_advance()
中的各个版本,我们以迭代器的类型进行区分。
在STL
中是将五种迭代器分别定义为class
:
struct input_iterator_tag{};
···
struct random_access_iterator_tag:public bidirectional iterator_tag{};
//这些class只用来作为标记,所以为空。
这样做的好处是:
* 有利于函数的重载。
* 通过继承,我们可以不必再写“单纯传递函数”,例如advance
的forward iterator
就可以省略,这样他就因为继承关系自动传递调用input iterator
版本的advance
函数。
STL
中的distance()
也是如此,可以根据不同迭代器类型,有不同的计算方式,带来不同的效率。
在使用“萃取器”的过程中,我们不必自行定义这五种内嵌相应型别,因为STL
提供了一个iterator class
,因此,我们可以这样写:
template<class Item>
struct ListIter:public std::iterator<std::forward_iterator_tag,Item>
{···};
3.7 __type_traits
iterator_traits
用来萃取迭代器的特性,__type_traits
用来萃取型别的特性(是否具有默认构造函数、拷贝构造函数、赋值操作、析构函数)。
__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_copy_constructor;
typedef __false_type has_trivial_assignment_operator;
typedef __false_type has_trivial_destructor;
typedef __false_type is_POD_type;
}
struct __true_type{};
struct __false_type{};
//用来标记真假,但是要返回带有真假性质的对象,因此定义为class
在SGI STL
中已经对各种基本类型定义了特化版本。这些基本类型都是POD type
,所以成员的值全是__true_type
。
实例:
template<class T> inline void copy(T* source,T* destination,int n){
copy(source,destination,n,
typename __type_traits<T>::has_trivial_copy_constructor());
}
//拥有非平凡拷贝构造函数
template<class T>void copy(T* source,T* destination,int n,
__false_type)
{···}
//拥有平凡拷贝构造函数
template<class T>void copy(T* source,T* destination,int n,
__true_type)
{···}
如果我们想要在我们的程序中使用__type_traits
,当__type_traits
对自行定义的类型进行萃取特性时,大多数的编译器时=是做不到的,萃取出来的特性都是__false_type
。所以我们必须设计一个适合我们定义的类型的特化版本。