先看看遍历std::vector的代码:

std::vector<int> v;
for (vector<int>::iterator iter = v.begin(); iter != v.end(); ++iter)
{
    // do someting with iter
}

其实,只需要将std::vector进行替换,STL所有的container(顺序容器和关联容器)的遍历都可以套用上面的代码。这篇文章重点讨论vector::end()函数的实现。

STL container的遍历一般都是借助于与container相对应的iterator来完成,要进行遍历就会涉及到从哪儿开始和到哪儿结束—也就是遍历范围的问题,STL container对应的遍历范围为:[begin(), end()):

  • begin():返回的是指向container第一个元素的iterator;
  • end():仅仅是一个占位符(placeholder),返回的iterator指向的是container最后一个元素的后面一个元素(iterators refer to one past the last element),所以遍历时是不能访问这个元素的。下面着重分析下该end()函数实现原理。
  • begin() == end()意味着container是空的。

1. vector::end的相关的源代码

Ubuntu 16.04, g++ 5.3.1环境下,std::vector源代码位于:/usr/include/c++/5/bits/stl_vector.h。或者g++编译时带上-g选项,然后用gdb单步debug含vector的代码就可以找到vector源代码文件。先看看end()函数的实现(注意:下面源代码里的行号显示的都是/usr/include/c++/5/bits/stl_vector.h文件里的行号):

 559       /**
 560        *  Returns a read/write iterator that points one past the last
 561        *  element in the %vector.  Iteration is done in ordinary
 562        *  element order.
 563        */
 564       iterator
 565       end() _GLIBCXX_NOEXCEPT
 566       { return iterator(this->_M_impl._M_finish); }

end()函数的实现主要涉及到两个上下文:通用的iterator和vector底层存储结构。下面先列出相关的代码,然后再一步步详细分析:

  71   template<typename _Tp, typename _Alloc>
  72     struct _Vector_base
  73     {
  74       typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
  75         rebind<_Tp>::other _Tp_alloc_type;
  76       typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer
  77         pointer;
  78
  79       struct _Vector_impl
  80       : public _Tp_alloc_type
  81       {
  82     pointer _M_start;
  83     pointer _M_finish;
  84     pointer _M_end_of_storage;
         ......
 163     public:
 164       _Vector_impl _M_impl;
 189     };

 213   template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
 214     class vector : protected _Vector_base<_Tp, _Alloc>
 215     {
 221       typedef _Vector_base<_Tp, _Alloc>          _Base;
 227       typedef typename _Base::pointer                    pointer;
 231       typedef __gnu_cxx::__normal_iterator<pointer, vector> iterator;
1496     };

2. 类型_Vector_base::pointer的实现

vector底层数据的存储结构和iterator都依赖于类型_Vector_base::pointer,其定义如下:

typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template rebind<_Tp>::other _Tp_alloc_type;
typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer pointer;
  • typename _Tp:表示的是vector里存储的元素类型;
  • typename _Alloc:是负责内存分配和回收相关的,缺省情况下是std::allocator类。

这里使用了C++ traits技巧,引用C++之父的话来解释什么是traits:

Think of a trait as a small object whose main purpose is to carry information used by another object or algorithm to determine “policy” or “implementation details”. - Bjarne Stroustrup

traits主要是为使用者提供类型信息。

下面用一个实例化的vector<int>类型来分析_Tp_alloc_type和pointer这两个类型定义。因为类型pointer依赖类型_Tp_alloc_type,所以先实例化分析类型_Tp_alloc_type。

vector<int> v;

等价于

vector<int, std::allocator<int> > v;

那么实例化的_Tp_alloc_type的定义:

typedef typename __gnu_cxx::__alloc_traits<std::allocator<int> >::template rebind<int>::other _Tp_alloc_type;

__gnu_cxx::__alloc_traits定义在源文件/usr/include/c++/5/ext/alloc_traits.h中:

 94 template<typename _Alloc>
 95   struct __alloc_traits
 96 #if __cplusplus >= 201103L
 97   : std::allocator_traits<_Alloc>    <---- since C++11
 98 #endif
 99   {
102     typedef std::allocator_traits<_Alloc>           _Base_type;
......
167     template<typename _Tp>
168       struct rebind
169       { typedef typename _Base_type::template rebind_alloc<_Tp> other; };
......
210   };

展开后得到:

typedef typename std::allocator_traits<std::allocator<int> >::template rebind_alloc<int> _Tp_alloc_type;

std::allocator_traits定义在源文件/usr/include/c++/5/bits/alloc_traits.h中:

 62   template<typename _Alloc, typename _Tp>
 63     struct __alloctr_rebind<_Alloc, _Tp, true>
 64     {
 65       typedef typename _Alloc::template rebind<_Tp>::other __type;
 66     };

 82   template<typename _Alloc>
 83     struct allocator_traits
 84     {
......
199       template<typename _Tp>
200     using rebind_alloc = typename __alloctr_rebind<_Alloc, _Tp>::__type;
......
438     };

进一步展开得到:

typedef typename std::allocator<int>::template rebind<int>::other _Tp_alloc_type;

std::allocator定义在/usr/include/c++/5/bits/allocator.h:

 91   template<typename _Tp>
 92     class allocator: public __allocator_base<_Tp>
 93     {
103       template<typename _Tp1>
104         struct rebind
105         { typedef allocator<_Tp1> other; };
124     };

最终实例化的_Tp_alloc_type定义为:

typedef std::allocator<int> _Tp_alloc_type;

在实例化的vector<int, std::allocator<int> >定义中,本来就已经知道实例化的std::allocator<int>,为什么不直接使用呢?

这要从一系列带模板参数的rebind函数作用说起。这些rebind函数并不是仅仅服务于vector container,而是服务于所有STL container。STL的一些container并不仅仅只管理实例化时使用者指定的类型的内存(即使用者的数据),同时还需要管理它们内部数据结构所需要的内存。

比如std::list container(就是我们常说的双向链表),std::list还需要维护一个内部使用的_List_node数据结构(该_List_node包含指向前一个和后一个_List_node的指针,和使用者的数据)。实例化的std::list<int, std::allocator<int> >只有一个实例化的std::allocator<int>,但是std::list内部又需要std::allocator<_List_node>来管理内存。从上面代码分析过程可以看出,这些一系列带模板参数的rebind函数可以从一个实例化的std::allocator<_Tp1>推导得到另一个实例化的std::allocator<_Tp2>(当然,_Tp1和_Tp2可以是相同的类型,也可以是不同的类型)。

得到_Tp_alloc_type的类型后,那么展开pointer类型定义:

typedef typename __gnu_cxx::__alloc_traits<std::allocator<int> >::pointer pointer;

__alloc_traits::pointer相关的代码:

 94 template<typename _Alloc>
 95   struct __alloc_traits
 96 #if __cplusplus >= 201103L
 97   : std::allocator_traits<_Alloc>
 98 #endif
 99   {
......
101 #if __cplusplus >= 201103L
102     typedef std::allocator_traits<_Alloc>           _Base_type;
......
104     typedef typename _Base_type::pointer            pointer;
210   };

进一步展开得到:

typedef typename std::allocator_traits<std::allocator<int> >::pointer pointer;

std::allocator_traits::pointer相关的代码:

 82   template<typename _Alloc>
 83     struct allocator_traits
 84     {
 88       typedef typename _Alloc::value_type value_type; <==> std::allocator<int>::value_type <==> int
 89
 90 #define _GLIBCXX_ALLOC_TR_NESTED_TYPE(_NTYPE, _ALT) \
 91   private: \
 92   template<typename _Tp> \
 93     static typename _Tp::_NTYPE _S_##_NTYPE##_helper(_Tp*); \
 94   static _ALT _S_##_NTYPE##_helper(...); \
 95     typedef decltype(_S_##_NTYPE##_helper((_Alloc*)0)) __##_NTYPE; \
 96   public:
 97
 98 _GLIBCXX_ALLOC_TR_NESTED_TYPE(pointer, value_type*) <==> _GLIBCXX_ALLOC_TR_NESTED_TYPE(pointer, int*)
 99
100       /**
101        * @brief   The allocator's pointer type.
102        *
103        * @c Alloc::pointer if that type exists, otherwise @c value_type*
104       */
105       typedef __pointer pointer;
......
438     };

接着把宏_GLIBCXX_ALLOC_TR_NESTED_TYPE(pointer, int*)展开:

          private:
          template<typename _Tp>
          static typename _Tp::pointer _S_pointer_helper(_Tp*);  # 1 <-----------------+
          static int* _S_pointer_helper(...);                    # 2                   |
                                                                 #                     |
          typedef decltype(_S_pointer_helper((std::allocator<int>*)0)) __pointer; # ---+
          public:

因为带模板参数的_S_pointer_helper函数更精确匹配,所以可以更进一步展开得到:

typedef std::allocator<int>::pointer __pointer; <==> typedef int * __pointer;

最终得到:

typedef int * pointer;

得到pointer类型后,实例化vector::iterator展开得到:

typedef __gnu_cxx::__normal_iterator<int *, vector> iterator;

__gnu_cxx::__normal_iterator定义在/usr/include/c++/5/bits/stl_iterator.h。__normal_iterator类型比较简单,就是对指针类型进一步封装而已,使得iterator可以像指针一样使用。

同时_M_start,_M_finish和_M_end_of_storage均int *类型,下面从vector代码看它们的作用:

  71   template<typename _Tp, typename _Alloc>
  72     struct _Vector_base
  73     {
......
 181     private:
 182       void
 183       _M_create_storage(size_t __n)
 184       {
 185     this->_M_impl._M_start = this->_M_allocate(__n);
 186     this->_M_impl._M_finish = this->_M_impl._M_start;
 187     this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
 188       }
 189     };

 213   template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
 214     class vector : protected _Vector_base<_Tp, _Alloc>
 215     {
......
 546       iterator
 547       begin() _GLIBCXX_NOEXCEPT
 548       { return iterator(this->_M_impl._M_start); }
 564       iterator
 565       end() _GLIBCXX_NOEXCEPT
 566       { return iterator(this->_M_impl._M_finish); }
......
 912       void
 913       push_back(const value_type& __x)
 914       {
 915     if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
 916       {
 917         _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
 918                                  __x);
 919         ++this->_M_impl._M_finish;
 920       }

从上面代码可以看出,_M_start指向vector的第一个元素;_M_finish指向vector末尾的下一个可用的位置(也就是我们常说的,最后一个元素的下一元素),这也是为什么在遍历时遇到_M_finish时就表示遍历已经结束的原因。

3. 遍历结束条件是用!=还是<?

先看下面的测试代码:

1
2
3
4
5
6
7
8
9
10
std::vector<int> v;
for (std::vector<int>::iterator iter = v.begin(); iter < v.end(); ++iter)
{
    // do someting with iter
}
std::list<int> l;
for (std::list<int>::iterator iter = l.begin(); iter < l.end(); ++iter)
{
    // do someting with iter
}

第7行出现编译error:

1
2
3
vector.cc:7:58: error: no match for operator< (operand types are std::__cxx11::list<int>::iterator {aka std::_List_iterator<int>} and std::__cxx11::list<int>::iterator {aka std::_List_iterator<int>})
     for (std::list<int>::iterator iter = l.begin(); iter < l.end(); ++iter)
                                                          ^

原因是:只有std::vector和std::deque同时支持<!=,而其它的container只支持!=

4. vector::iterator越界问题

先看下面代码:

std::vector<int> v {1, 2, 3};
for (std::vector<int>::iterator iter = v.begin(); iter != v.end(); iter += 2)
{
    std::cout << *iter << " ";
}

上面代码中,iter永远也不会等于v.end()。

当遍历结束判断条件失效时,会导致遍历无法结束,进而出现访问越界问题,最终导致程序crash。对支持<运算符的container可以用<代替!=;对不支持<运算符的container只能使用者自己保证越界问题。