先看看遍历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只能使用者自己保证越界问题。