《STL源码剖析》笔记-迭代器iterators

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/WizardtoH/article/details/82215615

上一篇:《STL源码剖析》笔记-空间配置器

迭代器简单来说就是提供一种方法,在不需要暴露容器的内部表现形式情况下,使之能依次访问容器中的各个元素。另外,通过迭代器容器和算法可以有机的粘合在一起,只要对算法给予不同的迭代器,就可以对不同容器进行相同的操作。

迭代器设计思维

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实现。

下一篇:《STL源码剖析》笔记-容器的分类

猜你喜欢

转载自blog.csdn.net/WizardtoH/article/details/82215615