迭代器简单来说就是提供一种方法,在不需要暴露容器的内部表现形式情况下,使之能依次访问容器中的各个元素。另外,通过迭代器容器和算法可以有机的粘合在一起,只要对算法给予不同的迭代器,就可以对不同容器进行相同的操作。
迭代器设计思维
STL中迭代器的中心思想是将容器和算法分开,彼此独立设计,最后通过迭代器结合在一起。以下为算法find,传入不同的迭代器就能在其范围中进行查找:
template<typename InputIterator, typename T>
InputIterator find(InputIterator first, InputIterator last, const T &value)
{
while (first != last && *frist != value)
++first;
return first;
}
迭代器是一种行为类似指针的对象,最重要的就是对operator*和oprator->进行重载。
traits编程技巧
traits编程按照字面理解就是特性编程,在迭代器中有很多应用。traits编程是怎么回事?下面让我们看看。
在算法中运用迭代器时,很可能会用到其相应型别(迭代器所指元素的类型)。假设算法中有必要声明一个变量,以“迭代器所指对象的型别”为型别,该怎么办呢?STL中利用函数模板(function template)的参数推导机制解决。
template <class I, class T>
void func_impl(I iter, T t) {
T tmp; // 这里就是迭代器所指物的类型新建的对象
// ... 功能实现
}
template <class I>
inline void func(I iter) {
func_impl(iter, *iter); // 传入iter和iter所指的值,class自动推导
}
int main() {
int i;
func(&i);
}
迭代器相应型别不只是“迭代器所指对象的型别”一种而已。根据经验,最常用的相应型别有五种,而函数模板参数推导机制推导的只是参数,无法推导函数的返回值类型。这种情况,可以通过声明内嵌型别来完成。
template <class T>
struct MyIter {
typedef T value_type; // 内嵌型别声明
T *m_ptr;
MyIter (T *p = 0) : m_ptr(p) {}
T& operator*() const { return *m_ptr;}
// ...
};
template <class I>
typename I::value_type // 函数func()的返回类型前面必须加上关键词typename,告知编译器这是一个型别
func(I ite) {
return *ite;
}
// ...
MyIter<int> ite(new int(8));
cout << func(ite);
以上的实现方式看起来的确解决了返回值类型的问题,但是实际上不是所有的迭代器都是类类型。比如原生指针就不是,因此无法定义内嵌类型,但是STL需要接受原生指针作为迭代器(例如数组作为STL算法的参数)。这种情况可以通过模板偏特化来解决,模板偏特化是指:如果模板拥有一个以上的模板参数,那么可以针对其中数个参数进行特殊化,也就是说我们可以在泛型中特化出一个模板版本。
template <class I>
struct iterator_traits {
typedef typename I::value_type value_type;
};
// 偏特化版本
template <class T>
struct iterator_traits<T*> {
typedef T value_type;
};
template <class I>
typename iterator_traits<I>::value_type
func(I ite) {
return *ite;
}
int main()
{
int a = 1;
std::cout << func(&a) << std::endl;
return 0;
}
以上的代码中,iterator_traits有两个版本,一个是将模板参数 I
中的成员value_type定义成value_type,另一个偏特化版本是将模板参数T*
的类型T定义成value_type。另外,func函数的返回值定义成了iterator_traits::value_type,这样就能接收偏特化版本的value_type。当func的参数类型是int*时,会优先匹配偏特化版本,这样value_type类型就被推导为int,因此能够支持原生指针。还需要注意的是如果传入类型为const int*,那么类型会被推导为const int,这样会导致在模板内部无法对它进行赋值,因此还需要特化一个const版本:
template <class T>
struct iterator_traits<const T*> {
typedef T value_type; // const int*类型会被推导为int
};
通过以上的结束,可以看到,traits的作用其实是通过模板的推导能力获取迭代器相应型别。
迭代器的相应型别
traits能够获取到迭代器的相应型别,但是前提是所有的迭代器必须遵守一定的规则。
上图是迭代器traits获取相应型别的示意图,最常用到的相应型别有:
- value_type
- difference_type
- reference_type
- pointer_type
- iterator_category
如果自己开发容器,那么容器对应的迭代器必须定义这五种相应型别,才能和STL较好的配合。
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;
};
value_type
value_type是指迭代器所指对象的型别。任何一个想要与STL算法配合的类,都应该定义自己的value_type内嵌类型,就和上文介绍的一样。
difference_type
difference_type的作用是表示两个迭代器之间的距离,也可以用来表示一个容器的最大容量。如果一个泛型算法提供技术功能,传回值就必须使用迭代器的difference_type。
// STL中的count函数
template <class I, calss T>
typename iterator_traits<I>::difference_type
count(I first, I last, const T& value){
typenameiterator_traits<I>::difference_type n = 0;
for( ; first != last; ++first)
if(*first == value)
++n;
return n;
}
以下为difference_type 定义,有了定义任何迭代器difference_type都能表示为typename iterator_traits <I>::difference_type
。
template <class I>
struct iterator_traits {
typedef typename I::difference_type difference_type;
};
// 原生指针的difference_type为C++内建类型ptrdiff_t
template <class T>
struct iterator_traits<T*> {
typedef ptrdiff_t difference_type;
};
template <class T>
struct iterator_traits<const T*> {
typedef ptrdiff_t difference_type;
};
reference_type
从迭代器所指的元素是否允许改变的角度看,迭代器分为两种:constant iterators和mutable iterators。当我们对一个mutable iterators进行operator*操作,获取到的应该是一个左值,因为右值不允许赋值。在C++中,函数如果要传回左值都是以引用的方式进行,所以当类型T的迭代器是一个mutable iterators时,operator*放回的类型应该是T&。
这就是迭代器中的reference_type,具体实现将和pointer_type一起介绍。
pointer_type
pointer和reference有非常比企鹅的关联,当迭代器需要返回容器内的元素本身来对元素的值进行修改时,即具有写入功能的迭代器,就应该在*p时,返回一个p所指元素的引用,既然引用可以,那么用指针也可以。
template<typename I>
struct iterator_traits
{
...
typedef typename I::pointer pointer;
typedef typename I::reference reference;
};
template<typename T>
struct iterator_traits<T*> // 原始指针
{
...
typedef T* pointer;
typedef T& reference;
};
template<typename T>
struct iterator_traits<const T*> // 原始const指针
{
...
typedef const T* pointer;
typedef const T& reference;
};
iterator_category
iterator_category是迭代器所属分类的标示符,共有五种:
- Input iterator,这种迭代器所指的对象,不允许外界改变,只读。
- Output iterator,只写
- Forward iterator,允许写入型算法,在此种迭代器所形成的区间上进行读写操作。
- Bidirectional iterator,可双向移动,某些算法需要逆向走访某个迭代区间,选用。
- Random Access iterator,前四中迭代器都只供应一部分指针算术能力(前三种支持operate++,第四种再加上operate- -),第五种覆盖所有的指针算术能力,包括p+n、p-n、p[n]、p1-p2、p1 < p2。
这些迭代器类型的从属关系如下图,箭头指向的方向功能越来越强。
STL的算法是追求效率的极致,为了效率最高,每一中迭代器都要定义一个明确的类型。假设有个算法可以接受Input iterator,当然也能接受Random Access iterator,但是这样效率可能不是最高的。例如advance算法:
template <class InputIterator, class Distance>
inline void advance_II(InputIterator& i, Distance n) {
while (n--) ++i; // 单向逐一前进
}
template <class BidirectionalIterator, class istance>
inline void advance_BI(BidirectionalIterator& i, Distancen n) {
// 双向逐一前进
if (n >= 0)
while (n--) ++i;
else
while (n++) --i;
}
template <class RandomAccessIterator, class Distance>
inline void advance_RAI(RandomAccessIterator& i, Distancen n) {
i += n; // 跳跃向前
}
以上列的几种方法,如果调用InputIterator版本,那么对于RandomAccessIterator来说效率太低,如果使用RandomAccessIterator版本,那么其他两个类型的迭代器无法支持。因此,需要对三种进行整合:
template <class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n) {
if (is_random_access_iterator(i))
advance_RAI(i, n);
else if (is_bidirectional_iterator(i))
advance_RAI(i, n);
else
advance_II(i, n);
}
这种做法在运行时选择,也需要一定的消耗。所以可以利用重载函数,并在上层函数设计中加入一个类型参数来区分不同的版本。
// 定义5种迭代器类型,互相有一定的继承关系(下文解释为何使用继承机制)
struct input_iter_tag {};
struct output_iter_tag {};
struct forward_iter_tag : public input_iter_tag {};
struct bidirectional_iter_tag : public forward_iter_tag {};
struct random_access_iter_tag : public bidirectional_iter_tag {};
template <class InputIterator, class Distance>
inline void __advance(InputIterator& i, Distance n, input_iter_tag) {
while (n--) ++i; // 单向逐一前进
}
template <class BidirectionalIterator, class istance>
inline void __advance(BidirectionalIterator& i, Distancen n, bidirectional_iter_tag) {
// 双向逐一前进
if (n >= 0)
while (n--) ++i;
else
while (n++) --i;
}
template <class RandomAccessIterator, class Distance>
inline void __advance(RandomAccessIterator& i, Distancen n, random_access_iter_tag) {
i += n; // 跳跃向前
}
// 对外的上层接口
template <class InputIterator, class Distance>
inline void advance(InputIterator& i, _Distance n) {
__advance(i, n, iterator_traits<I>::iterator_category()); // 根据传入迭代器的iterator_category型别判断调用哪一个重载函数
}
对外接口advance的模板类型使用了InputIterator,这是因为STL规定要以算法支持的最低阶迭代器类型来为模板参数命名。
另外,为了满足以上的功能,同样需要在iterator_traits中增加相应的型别:
template<typename I>
struct iterator_traits
{
...
typedef typename I::iterator_category iterator_category;
};
template<typename T>
struct iterator_traits<T*> // 原始指针是一种random access iterator
{
typedef random_access_iter_tag iterator_category;
};
template<typename T>
struct iterator_traits<const T*> // 原始const指针
{
typedef random_access_iter_tag iterator_category;
};
迭代器的类型应该属于各种类型中特性最多的那个,例如原始指针int*,即是random_access_iter_tag,又是input_iter_tag,那么它的类型应该是random_access_iter_tag。
而5种迭代器类型定义成继承关系,一点是能够帮助实现__advance函数重载;另一个好处是不用实现其他单纯只做传递的函数版本,例如forward_iter_tag版本,因为当重载版本中找不到forward_iter_tag版本时,也能自动匹配上父类input_iter_tag版本的函数。
std::iterator的保证
任何迭代器都应该提供五个内嵌相应型别,否则可能无法与其它STL组件顺利搭配,但是总会容易遗漏。为了简化编写过程,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>
{}
设计适当的型别是迭代器的责任,设计适当的迭代器是容器的责任,只有容器本身才知道设计出怎么样的迭代器来遍历自己,并执行各种行为。至于算法完全可以独立于容器和迭代器之外自行发展,只要设计时以迭代器为对外接口就可以了。traits编程技法大量运用于STL实现中,它利用“内嵌类型”的编程技巧与编译器的参数推导功能,增强C++未能提供的关于型别认证方面的能力,弥补C++不为强类型语言的遗憾。
SGI STL中的迭代器型别的特性获取__type_tratis
STL对迭代器制定了规范,定义了iterator_traits来负责获取迭代器特性。而SGI STL把traits扩展到了型别中,用__type_tratis负责获取型别的特性。这些特性包括以下内容:
__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; // 是否为平POD类型
关于POD类型,详见https://blog.csdn.net/WizardtoH/article/details/80767740。
根据上述特性,就能够对不同型别进行不同的构造、析构、拷贝、赋值操作,采用最有效率的方式进行处理。例如,不调用自动生成的平凡构造函数和析构函数,而是直接使用malloc和memcpy等方式获得最高的效率,这在大规模且操作频繁的容器中有很大的帮助。
SGI STL中的__type_tratis就提供了一种机制,运行针对不同的型别,在编译器完成调用哪个函数的选择。首先需要使用上述的几个特性,并定义出真/假的类型:
// 定义成类型才能够支持编译器进行参数推导,而实现重载
struct __true_type {
};
struct __false_type {
};
为了将上面的两个定义结合在一起,__type_traits 需要定义五个类型,类型的值默认为__false_type。定义成__false_type是最保守的,后续再根据每一个特性进行特化。
template <class type>
struct __type_traits {
typedef __true_type this_dummy_member_must_be_first; // 通知编译器这个__type_traits模板是特殊的,保证编译器也使用了一个同名的模板时也能正常运行
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;
};
一下举例几种特化版本,都是C++中的基本型别,它们的每一个成员都是__true_type,所以都可以采用最快速的方式进行拷贝或赋值。
__STL_TEMPLATE_NULL struct __type_traits<char> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
_STL_TEMPLATE_NULL struct type_traits <unsigned char>
{
typedef _true_type has_trivial_default_constructor;
typedef _true_type has_trivial_copy_constructor;
typedef _true_type has_trivial_assgignment_constructor;
typedef _true_type has_trivial_destructor;
typedef _true_type is_POD_type;
};
_STL_TEMPLATE_NULL struct type_traits <short>
{
typedef _true_type has_trivial_default_constructor;
typedef _true_type has_trivial_copy_constructor;
typedef _true_type has_trivial_assgignment_constructor;
typedef _true_type has_trivial_destructor;
typedef _true_type is_POD_type;
};
template <class T>
struct__type_traits<T*> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
__type_tratis在SGI STL中应用很广,例如上一篇中说到的uninitialized_fill_n函数:
template <class ForwardIterator, class Size, class T>
inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n, const T& x) {
return __uninitialized_fill_n(first, n, x, value_type(first));
}
首先用value_type萃取出迭代器的value type型别,再利用__type_tratis判断是否为POD类型
template <class ForwardIterator, class Size, class T, class T1>
inline ForwardIterator __uninitialized_fill_n(ForwardIterator first, Size n, const T& x, T1*) {
typedef typename __type_traits<T1>::is_POD_type is_POD;
return __uninitialized_fill_n_aux(first, n, x, is_POD());
}
//如果正是POD类型的话,只要简单的操作
template <class ForwardIterator, class Size, class T>
inline ForwardIterator __uninitialized_fill_n_aux(ForwardIteratorfirst, Size n, const T& x, __true_type) {
return fill_n(first, n, x);
}
//如果不是POD类型,那么就需要老老实实的一个一个在未初始化的内存中调用构造函数
template <class ForwardIterator, class Size, class T>
ForwardIterator __uninitialized_fill_n_aux(ForwardIteratorfirst, Size n, const T& x, __false_type) {
ForwardIterator cur = first;
__STL_TRY {
for ( ; n > 0; --n, ++cur)
construct(&*cur, x);
return cur;
}
__STL_UNWIND(destroy(first, cur));
}
// fill_n函数
template <class OutputIterator, class Size, class T>
OutputIterator fill_n(OutputIterator first, Size n, const T& value) {
for ( ; n > 0; --n, ++first)
*first = value;
return first;
}
假如自己在程序中定义了一个类,大部分编译器__type_traits获取到的特性都是__false_type,除非自己针对这个类定义一个特化的版本。
C++11提供的相关特性
实际上在C++11中已经有了相关的一些新特性,可以代替这种自定义的特性。
- 迭代器的value_type型别,可以使用decltype进行推算,那么pointer和refrence都能够得到。
- __type_traits中的has_trivial_default_constructor,可以用std::is_trivially_default_constructible代替。
- __type_traits中的has_trivial_copy_constructor,可以用std::is_trivially_copy_constructible实现。
- __type_traits中的has_trivial_assignment_operator,可以用std::is_trivially_copy_assignable实现。
- __type_traits中的has_trivial_destructor,可以用std::is_trivially_destructible实现。
- __type_traits中的is_POD_type,可以用std::is_pod实现。