带你深入理解STL之迭代器和Traits技法

时间:2022-07-22 05:13:13

在开始讲迭代器之前,先列举几个例子,由浅入深的来理解一下为什么要设计迭代器。

//对于int类的求和函数
int sum(int *a , int n)
{
    int sum = 0 ;
    for (int i = 0 ; i < n ; i++) {
        sum += *a++;
    }
    return sum;
}
//对于listNode类的求和函数
struct ListNode {
    int val;
    ListNode * next;
};
int sum(ListNode * head) {
    int sum = 0;
    ListNode *p = head;
    while (p != NULL) {
        sum += p->val;
        p=p->next;
    }
    return sum;
}

针对如上两个函数,我们来谈谈这种设计的缺陷所在:

  • 遍历int数组和单链表listNode时,需要设计两份不一样的sum求和函数,对于STL这样含有大量容器的代码库,针对每一种容器都设计sum的话,过于冗杂
  • 在sum函数中暴露了太多设计细节,如ListNode的节点值类型int,和指向下一个节点的指针next
  • 对于int数组来说,还必须知道数组的大小,以免越界访问
  • 算法的设计过多的依赖容器,容器的改动会造成大量算法函数的随之改动

那么,如何设计才能使得算法摆脱特定容器?如何让算法和容器各自独立设计,互不影响又能统一到一起?本篇博客就带你一窥STL的迭代器设计。

迭代器概述

迭代器是一种抽象的设计概念,在设计模式中是这么定义迭代器模式的,即提供一种方法,使之能够巡访某个聚合物所含的每一个元素,而无需暴露该聚合物的内部表述方式。

不论是泛型思维或STL的实际运用,迭代器都扮演着重要的角色,STL的中心思想就在于:将数据容器和算法分开,彼此独立设计,最后再以一帖胶着剂将它们撮合在一起。

谈到迭代器需要遍历容器就想到指针,的确,迭代器就是一种类似指针的对象,而指针的各种行为中最常见也最重要的就是内容提领(dereference)和成员访问(member access),因此,迭代器最重要的编程工作就是对operator*和operator->进行重载工作。

Traits编程技法

在介绍STL迭代器之前,先来看看Traits编程技法,通过它你能够更好的理解迭代器设计。

template参数推导机制

我们先回过头去看看sum函数,在sum函数中,我们必须知道容器的元素类型,这关系到函数返回值。既然迭代器设计中不能暴露容器的实现细节,那么我们在算法中是不可能知道容器元素的类型,因此,必须设计出一个机制,能够提取出容器中元素的类型。看看如下示例代码:

#include <stdio.h>
#include <iostream>

using namespace std;

//此函数中并不知道iter所指的元素型别,而是通过模板T来获取的
template <class I, class T1 ,class T>
T sum_impl(I iter ,T1 n , T t) {
    T sum = 0;//通过模板的特性,可以获取I所指之物的型别,此处为int

    //这里做func应该做的工作
    for(int i = 0 ; i < n ;i++){
        sum+=*iter++;
    }
    return sum;
}

template <class I , class T1>
inline T1 sum(I iter , T1 n) {//此处暴露了template参数推导机制的缺陷,在型别用于返回值时便束手无策
    return sum_impl(iter , n ,*iter);
}

int main() {
    int a[5] = {1,2,3,4,5};
    int sum1 = sum(a , 5);
    cout<<sum1<<endl;
}

上例中通过模板的参数推导机制推导出了iter所指之物的型别(int),于是可以定义T sum变量。

然而,迭代器所指之物的型别并非只是”迭代器所指对象的型别“,最常用的迭代器型别有五种,并非任何情况下任何一种都可利用上述的template参数推导机制来获取,而且对于返回值类型,template参数推导机制也束手无策,因此,Traits技法应运而生,解决了这一难题!

内嵌型别机制

Traits技法采用内嵌型别来实现获取迭代器型别这一功能需求,具体怎么实现的呢?我们看下面的代码:

#include <stdio.h>
#include <iostream>

using namespace std;
//定义一个简单的iterator
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返回值型别
func(I iter){
    return *iter;
}

int main(){
    MyIter<int> iter(new int(8));
    cout<<func(iter)<<endl;
}

注意,func()函数的返回值型别必须加上关键词typename,因为T是一个template参数,在它被编译器具现化之前,编译器对其一无所知,关键词typename的用意在于告诉编译器这是一个型别,如此才能通过编译。

内嵌型别看起来不错,可以很顺利的提取出迭代器所指型别并克服了template参数推导机制不能用于函数返回值的缺陷。可是,并不是所有的迭代器都是class type,原声指针就不是!如果不是class type,那么就无法声明内嵌型别。但是STL绝对必须接受原生指针作为一个迭代器。因此,必须另行它法!

iterator_traits萃取机

针对原生指针这类特殊情况,我们很容易想到利用模板偏特化的机制来实现特殊声明,在泛化设计中提供一个特化版本。偏特化的定义是:针对任何template参数更进一步的条件限制所设计出来的一个特化版本。这里,针对上面的MyIter设计出一个适合原生指针的特化版本,如下:

template <class T>
struct MyIter <T*>{   //T*代表T为原生指针,这便是T为任意型别的一个更进一步的条件限制
    typedef T value_type; // 内嵌型别声明
    T* ptr;
    MyIter(T* p =0):ptr(p){}
    T& operator*() const {return *ptr;}
};

有了上述的介绍,包括template参数推导,内嵌型别,模板偏特化等,下面STL的真正主角要登场了,STL专门设计了一个iterator_traits模板类来”萃取“迭代器的特性。其定义如下:

// 用于traits出迭代其所指对象的型别
template <class I>
struct iterator_traits
{
  typedef typename I::value_type        value_type;
};

这个类如何完成迭代器的型别萃取呢?我们继续往下看:

template <class I>
typename iterator_traits<I>::value_type // 通过iterator_traits类萃取I的型别
func(I iter){
    return *iter;
}

从表面上来看,这么做只是多了一层间接性,但是带来的好处是极大的!iterator_traits类可以拥有特化版本,如下:

//原生指针特化版本
template <class T>
struct iterator_traits <T*>
{
  typedef T  value_type;
}
//const指针特化版本
template <class T>
struct iterator_traits <const T*>
{
  typedef T  value_type;
}

于是,原生指针int*虽然不是一种class type,也可以通过traits取其value,到这里traits的思想就基本明了了!下面就来看看STL只能中”萃取机“的源码

// 用于traits出迭代其所指对象的型别
template <class Iterator>
struct iterator_traits
{
  // 迭代器类型, STL提供五种迭代器
  typedef typename Iterator::iterator_category iterator_category;

  // 迭代器所指对象的型别
  // 如果想与STL算法兼容, 那么在类内需要提供value_type定义
  typedef typename Iterator::value_type        value_type;

  // 这个是用于处理两个迭代器间距离的类型
  typedef typename Iterator::difference_type   difference_type;

  // 直接指向对象的原生指针类型
  typedef typename Iterator::pointer           pointer;

  // 这个是对象的引用类型
  typedef typename Iterator::reference         reference;
};
// 针对指针提供特化版本
template <class T>
struct iterator_traits<T*>
{
  typedef random_access_iterator_tag iterator_category;
  typedef T                          value_type;
  typedef ptrdiff_t                  difference_type;
  typedef T*                         pointer;
  typedef T&                         reference;
};

// 针对指向常对象的指针提供特化
template <class T>
struct iterator_traits<const T*>
{
  typedef random_access_iterator_tag iterator_category;
  typedef T                          value_type;
  typedef ptrdiff_t                  difference_type;
  typedef const T*                   pointer;
  typedef const T&                   reference;
};

对于源码中的五种迭代器型别在下一小节中会有详细说明。

迭代器设计

迭代器型别

value_type

所谓value_type,是指迭代器所指对象的型别,在之前的示例中已经介绍得很清楚了,这里就不再赘述。

difference_type

difference_type用来表示两个迭代器之间的距离,因此它也可以用来表示一个容器的最大容量。下面以STL里面的计数功能函数count()为例,来介绍一下difference_type的用法。

template <class I,class T>
typename iterator_traits<I>::difference_type   //返回值类型
count(I first , I end , const T& value){
    typename iterator_traits<I>::difference_type n = 0;
    for( ; first!=end ; ++first){
        if(*first == value) ++n;
    }
    return n;
}

针对原生指针和原生的const指针,iterator_traits的difference_type为ptrdiff_t(long int),特化版本依然可以采用iterator_traits::difference_type来获取型别。

reference_type

从“迭代器所指之物的内容是否可以改变”的角度可以将迭代器分为两种:

  • const迭代器:不允许改变“所指对象之内容”,例如const int* p
  • mutable迭代器:允许改变“所指对象之内容”,例如int* p

当我们要对一个mutable迭代器进行提领(reference)操作时,获得的不应该是一个右值(右值不允许赋值操作),而应该是一个左值,左值才允许赋值操作。

故:

+ 当p是一个mutable迭代器时,如果其value type是T,那么*p的型别应该为T&;

+ 当p是一个const迭代器时,*p的型别为const T&

pointer type

迭代器可以传回一个指针,指向迭代器所指之物。再迭代器源码中,可以找到如下关于指针和引用的实现:

Reference operator*() const { return *value; }
pointer operator->() const { return &(operator*()); }

在iterator_traits结构体中需要加入其型别,如下:

template <class Iterator>
struct iterator_traits
{
  typedef typename Iterator::pointer           pointer;
  typedef typename Iterator::reference         reference;
}
//针对原生指针的特化版本
template <class T>
struct iterator_traits<T*>
{
  typedef T*                         pointer;
  typedef T&                         reference;
};
//针对原生const指针的特化版本
template <class T>
struct iterator_traits<const T*>
{
  typedef const T*                   pointer;
  typedef const T&                   reference;
};

iterator_category

这一型别代表迭代器的类别,一般分为五类:

  • Input Iterator:只读(read only)
  • Output Iterator:只写(write only)
  • Forward Iterator:允许“写入型”算法在此迭代器所形成的区间上进行读写操作
  • Bidirectional Iterator:可双向移动的迭代器
  • Random Access Iterator:前四种迭代器都只供应一部分指针的算数能力(前三种支持operator++,第四种支持operator–),第五种则涵盖所有指针的算数能力,包括p+n,p-n,p[n],p1-p2,p1
// 用于标记迭代器类型
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};

迭代器的保证

为了符合规范,任何迭代器都应该提供五个内嵌型别,以利于Traits萃取,否则就自别与整个STL架构,可能无法与其他STL组件顺利搭配。STL提供了一份iterator class如下:

//为避免挂一漏万,每个新设计的迭代器都必须继承自它
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;
};

迭代器源码(完整版)

/**
 * 用于标记迭代器类型
 */
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};

/**
 * 用于traits出迭代其所指对象的型别
 */
template <class Iterator>
struct iterator_traits
{
  // 迭代器类型, STL提供五种迭代器
  typedef typename Iterator::iterator_category iterator_category;

  // 迭代器所指对象的型别
  // 如果想与STL算法兼容, 那么在类内需要提供value_type定义
  typedef typename Iterator::value_type        value_type;

  // 这个是用于处理两个迭代器间距离的类型
  typedef typename Iterator::difference_type   difference_type;

  // 直接指向对象的原生指针类型
  typedef typename Iterator::pointer           pointer;

  // 这个是对象的引用类型
  typedef typename Iterator::reference         reference;
};

/**
 * 针对指针提供特化版本
 */
template <class T>
struct iterator_traits<T*>
{
  typedef random_access_iterator_tag iterator_category;
  typedef T                          value_type;
  typedef ptrdiff_t                  difference_type;
  typedef T*                         pointer;
  typedef T&                         reference;
};
/**
 * 针对指向常对象的指针提供特化
 */
template <class T>
struct iterator_traits<const T*>
{
  typedef random_access_iterator_tag iterator_category;
  typedef T                          value_type;
  typedef ptrdiff_t                  difference_type;
  typedef const T*                   pointer;
  typedef const T&                   reference;
};
/**
 *  返回迭代器类别
 */
template <class Iterator>
inline typename iterator_traits<Iterator>::iterator_category
iterator_category(const Iterator&)
{
  typedef typename iterator_traits<Iterator>::iterator_category category;
  return category();
}
/**
 * 返回表示迭代器距离的类型
 */
template <class Iterator>
inline typename iterator_traits<Iterator>::difference_type*
distance_type(const Iterator&)
{
  return static_cast<typename iterator_traits<Iterator>::difference_type*>(0);
}
/**
 * 返回迭代器所指对象的类型
 */
template <class Iterator>
inline typename iterator_traits<Iterator>::value_type*
value_type(const Iterator&)
{
  return static_cast<typename iterator_traits<Iterator>::value_type*>(0);
}

迭代器相关函数设计

distance函数

distance函数用来计算两个迭代器之前的距离。

  • 针对Input Iterator,Output Iterator,Forward Iterator,Bidirectional Iterator来说,必须逐一累加计算距离
  • 针对random_access_iterator来说,支持两个迭代器相减,所以直接相减就能得到距离

其具体实现如下:

/**
 * 针对Input Iterator,Output Iterator,Forward Iterator,Bidirectional Iterator
 * 这四种函数,需要逐一累加来计算距离
 */
template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
__distance(InputIterator first, InputIterator last, input_iterator_tag)
{
  iterator_traits<InputIterator>::difference_type n = 0;
  while (first != last) {
    ++first; ++n;
  }
  return n;
}
/**
 * 针对random_access_iterator,可直接相减来计算差距
 */
template <class RandomAccessIterator>
inline iterator_traits<RandomAccessIterator>::difference_type
__distance(RandomAccessIterator first, RandomAccessIterator last,
           random_access_iterator_tag)
{
  return last - first;
}
// 入口函数,先判断迭代器类型iterator_category,然后调用特定函数
template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last)
{
  typedef typename iterator_traits<InputIterator>::iterator_category category;
  return __distance(first, last, category());
}

Advance函数

Advance函数有两个参数,迭代器p和n,函数内部将p前进n次。针对不同类型的迭代器有如下实现:

  • 针对input_iterator和forward_iterator,单向,逐一前进
  • 针对bidirectional_iterator,双向,可以前进和后退,n>0和n<0
  • 针对random_access_iterator,支持p+n,可直接计算

其代码实现如下:

/**
 * 针对input_iterator和forward_iterator版本
 */
template <class InputIterator, class Distance>
inline void __advance(InputIterator& i, Distance n, input_iterator_tag)
{
  while (n--) ++i;
}
/**
 * 针对bidirectional_iterator版本
 */
template <class BidirectionalIterator, class Distance>
inline void __advance(BidirectionalIterator& i, Distance n,
                      bidirectional_iterator_tag)
{
  if (n >= 0)
    while (n--) ++i;
  else
    while (n++) --i;
}
/**
 * 针对random_access_iterator版本
 */
template <class RandomAccessIterator, class Distance>
inline void __advance(RandomAccessIterator& i, Distance n,
                      random_access_iterator_tag)
{
  i += n;
}
/**
 * 入口函数,先判断迭代器的类型
 */
template <class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n)
{
  __advance(i, n, iterator_category(i));
}

附加:type_traits设计

iterator_traits负责萃取迭代器的特性;type_traits负责萃取型别的特性。

带你深入理解STL之空间配置器(思维导图+源码)一篇博客中,讲到需要根据对象构造函数和析构函数的trivial和non-trivial特性来采用最有效的措施,例如:如果构造函数是trivial的,那么可以直接采用如malloc()和memcpy()等函数,来提高效率。

例如:如果拷贝一个未知类型的数组,如果其具有trivial拷贝构造函数,那么可以直接利用memcpy()来拷贝,反之则要调用该类型的拷贝构造函数。

type_traits的源代码实现如下:

/**
 * 用来标识真/假对象,利用type_traits响应结果来进行参数推导,
 * 而编译器只有面对class object形式的参数才会做参数推导,
 * 这两个空白class不会带来额外负担
 */
struct __true_type{};
struct __false_type{};

/**
 * type_traits结构体设计
 */
template <class type>
struct __type_traits
{
    // 不要移除这个成员
    // 它通知能自动特化__type_traits的编译器, 现在这个__type_traits template是特化的
    // 这是为了确保万一编译器使用了__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;
};
// 特化类型:
//         char, signed char, unsigned char,
//         short, unsigned short
//         int, unsigned int
//         long, unsigned long
//         float, double, long double
/**
 * 以下针对C++内置的基本数据类型提供特化版本,
 * 使其具有trivial default constructor,
 * copy constructor, assignment operator, destructor并标记其为POD类型
 */
__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;
};
//针对char的特化版本
__STL_TEMPLATE_NULL struct __type_traits<signed 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;
};
//针对unsigned char的特化版本
__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_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};
//针对short的特化版本
__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_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};
//针对unsigned short的特化版本
__STL_TEMPLATE_NULL struct __type_traits<unsigned short>
{
   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;
};
//针对int的特化版本
__STL_TEMPLATE_NULL struct __type_traits<int>
{
   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;
};
//针对unsigned int的特化版本
__STL_TEMPLATE_NULL struct __type_traits<unsigned int>
{
   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;
};
//针对long的特化版本
__STL_TEMPLATE_NULL struct __type_traits<long>
{
   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;
};
//针对unsigned long的特化版本
__STL_TEMPLATE_NULL struct __type_traits<unsigned long>
{
   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;
};
//针对float的特化版本
__STL_TEMPLATE_NULL struct __type_traits<float>
{
   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;
};
//针对double的特化版本
__STL_TEMPLATE_NULL struct __type_traits<double>
{
   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;
};
//针对long double的特化版本
__STL_TEMPLATE_NULL struct __type_traits<long double>
{
   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;
};

// 针对指针提供特化
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;
};

// 针对char *, signed char *, unsigned char *提供特化

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;
};

struct __type_traits<signed 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;
};

struct __type_traits<unsigned 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的迭代器中有很多优秀的值得学习的设计方式,如萃取机制,用类继承来定义迭代器类型等,通篇代码比较多,图片讲解较少,只能说迭代器的设计中基本上都是较为繁琐的型别声明和定义,认真看下去的话会收获很多!若有疑惑可以在博文下方留言,我看到会及时帮大家解答!

参考:

+ C++ STL源码剖析

+ STL源码剖析——迭代器