前言
vector
的数据安排以及操作方式,与 array
非常相似。两者的唯一差别在于空间的运用的灵活性。array
是静态空间,一旦配置了就不能改变;vector
是动态空间,随着元素的加人,它的内部机制会自行扩充空间以容纳新元素。因此,vector
的运用对于内存的合理利用与运用的灵活性有很大的帮助,vector
的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率。
———— 《STL源码剖析》侯捷
本篇博客将会分析 STL 中 vector
的实现。
vector 的型别定义与数据结构
这里摘取部分 TinySTL::vector
的型别及成员变量定义部分代码进行分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| template <class T> class vector {
static_assert(!std::is_same<bool, T>::value, "vector<bool> is abandoned in tinystl"); public: typedef tinystl::allocator<T> allocator_type; typedef tinystl::allocator<T> data_allocator;
typedef typename allocator_type::value_type value_type; typedef typename allocator_type::pointer pointer; typedef typename allocator_type::const_pointer const_pointer; typedef typename allocator_type::reference reference; typedef typename allocator_type::const_reference const_reference; typedef typename allocator_type::size_type size_type; typedef typename allocator_type::difference_type difference_type;
typedef value_type* iterator; typedef const value_type* const_iterator; typedef tinystl::reverse_iterator<iterator> reverse_iterator; typedef tinystl::reverse_iterator<const_iterator> const_reverse_iterator;
allocator_type get_allocator() { return data_allocator(); }
private: iterator begin_; iterator end_; iterator cap_;
public: ...
private: ... };
|
可以看出,vector
的型别定义与数据结构都较为简单,下面将分析其中重要的两个部分。
vector 的迭代器
1 2
| typedef value_type* iterator; typedef const value_type* const_iterator;
|
由上面的型别定义可以看出,vector
的迭代器就是普通的指针。由于 vector
维护的是一个连续线性空间,所以不论其元素型别为何,普通指针都可以作为 vector
的迭代器而满足所有必要条件,因为 vector
迭代器所需要的操作行为,如 operator*
, operator->
,operator++
,operator--
,operator+
,operator-
,operator+=
,operator-=
,普通指针天生就具备。vector
支持随机存取,而普通指针正有着这样的能力。所以,vector
提供的是 Random Access lterators。
vector 的数据结构
1 2 3 4
| private: iterator begin_; iterator end_; iterator cap_;
|
vector
所采用的数据结构非常简单:线性连续空间。它以两个迭代器 begin_
和 end_
分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器 cap_
指向整块连续空间(含备用空间〉的尾端。
这里的变量名称与 SGI-STL 中并不一致,但作用相同。
为了降低空间配置时的速度成本,vector
实际配置的大小可能比客户端需求量更大一些,以备将来可能的扩充。这便是容量 (capacity) 的观念。换句话说,一个 vector
的容量水远大于或等于其大小。一且容量等于大小,便是满载,下次再有新增元素,整个vector
就得另觅居所,见图4-2。
使用 begin_
、end_
、cap_
三个迭代器,便可以轻易地提供收尾标示、大小、容量、空容器判断、注标 ([]) 运算子、最前端元素值、最后端元素值等功能。

vector 的构造、析构与内存管理
构造与析构函数
vector
提供了多种构造的方式,下面将连同析构函数逐一分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| vector() noexcept { try_init(); }
explicit vector(size_type n) { fill_init(n, value_type()); }
vector(size_type n, const value_type& value) { fill_init(n, value); }
template <class Iter, typename std::enable_if< tinystl::is_input_iterator<Iter>::value, int>::type = 0> vector(Iter first, Iter last) { TINYSTL_DEBUG(!(last < first)); range_init(first, last); }
vector(const vector& rhs) { range_init(rhs.begin_, rhs.end_); }
vector(vector&& rhs) noexcept : begin_(rhs.begin_), end_(rhs.end_), cap_(rhs.cap_) { rhs.begin_ = nullptr; rhs.end_ = nullptr; rhs.cap_ = nullptr; }
vector(std::initializer_list<T> ilist) { range_init(ilist.begin(), ilist.end()); };
~vector() { destroy_and_recover(begin_, end_, cap_ - begin_); begin_ = end_ = cap_ = nullptr; }
|
首先,默认构造函数 vector()
由 try_init()
完成,来看看这个函数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
template <class T> void vector<T>::try_init() noexcept { try { begin_ = data_allocator::allocate(16); end_ = begin_; cap_ = begin_ + 16; } catch (...) { begin_ = nullptr; end_ = nullptr; cap_ = nullptr; } }
|
可以看出,默认构造函数会申请 16 个空间,也就是说,默认构造下的 vector
初始容量为 16,并且具有 commit or rollback 机制。即,如果没有申请到全部的空间,那么就不申请任何空间,将所有的迭代器均置为空指针。
其次,vector(n)
与 vector(n, value)
这两个最常用的构造函数使用 fill_init()
完成,看看这个函数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
template <class T> void vector<T>::fill_init(size_type n, const value_type& value) { const size_type init_size = tinystl::max(static_cast<size_type>(16), n); init_space(n, init_size); tinystl::uninitialized_fill_n(begin_, n, value); }
template <class T> void vector<T>::init_space(size_type size, size_type cap) { try { begin_ = data_allocator::allocate(cap); end_ = begin_ + size; cap_ = begin_ + cap; } catch (...) { begin_ = nullptr; end_ = nullptr; cap_ = nullptr; throw; } }
|
fill_init()
先调用 init_space()
函数,申请指定大小的空间,值得注意的是这里申请空间失败的话会抛出异常;再利用 uninitialized_fill_n()
在申请到的空间上构造元素,uninitialized_fill_n()
会根据第一参数的型别特性,决定使用算法 fill_n()
或反复调用 construct()
来完成任务。
还有一些构造函数使用到了 range_init()
,原理与 fill_init()
类似,不同之处在于调用的是 uninitialized_copy()
,也会根据参数的性别特性来调用最有效率的函数。
1 2 3 4 5 6 7 8 9 10 11 12
|
template <class T> template <class Iter> void vector<T>::range_init(Iter first, Iter last) { const size_type len = tinystl::distance(first, last); const size_type init_size = tinystl::max(static_cast<size_type>(16), len); init_space(len, init_size); tinystl::uninitialized_copy(first, last, begin_); }
|
析构函数就是直接调用deallocate
空间配置器,先从头到尾析构所有数据,最后再将内存还给空间配置器。
vector
因为是类,所以我们并不需要手动的释放内存,生命周期结束后就自动调用析构从而释放调用空间,当然我们也可以直接调用析构函数释放内存。
1 2 3 4 5 6 7 8 9 10
|
template <class T> void vector<T>::destroy_and_recover(iterator first, iterator last, size_type n) { data_allocator::destroy(first, last); data_allocator::deallocate(first, n); }
|
内存管理
我们以 push_back()
为例分析一下 vector
的内存管理,该函数首先检是否还有备用空间,如果有就直接在备用空间上构造元素,并调整迭代器 end_
,使 vector
变大。如果没有备用空间了,就扩充空间(重新配置,移动数据、释放原空间)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
|
template <class T> void vector<T>::push_back(const value_type& value) { if (end_ != cap_) { data_allocator::construct(tinystl::address_of(*end_), value); ++end_; } else { reallocate_insert(end_, value); } }
template <class T> void vector<T>::reallocate_insert(iterator pos, const value_type& value) { const auto new_size = get_new_cap(1); auto new_begin = data_allocator::allocate(new_size); auto new_end = new_begin; const value_type& value_copy = value; try { new_end = tinystl::uninitialized_move(begin_, pos, new_begin); data_allocator::construct(tinystl::address_of(*new_end), value_copy); ++new_end; new_end = tinystl::uninitialized_move(pos, end_, new_end); } catch (...) { data_allocator::deallocate(new_begin, new_size); throw; } destroy_and_recover(begin_, end_, cap_ - begin_); begin_ = new_begin; end_ = new_end; cap_ = new_begin + new_size; }
template <class T> typename vector<T>::size_type vector<T>::get_new_cap(size_type add_size) { const auto old_size = capacity(); THROW_LENGTH_ERROR_IF(old_size > max_size() - add_size, "vector<T>'s size too big"); if (old_size > max_size() - old_size / 2) { return old_size + add_size > max_size() - 16 ? old_size + add_size : old_size + add_size + 16; } const auto new_size = old_size == 0 ? tinystl::max(add_size, static_cast<size_type>(16)) : tinystl::max(old_size + old_size / 2, old_size + add_size); return new_size; }
|
注意,所谓动态增加大小,并不是在原空间之后接续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍(这里设为 1.5 倍)另外配置一块较大空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此,对 vector
的任何操作,一旦引起空间重新配置,指向原 vector
的所有迭代器就都失效了。这是程序员易犯的一个错误,务需小心。
vector 一些函数的实现
这部分介绍一下 vector
内的常用函数。
insert
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
template <class T> typename vector<T>::iterator vector<T>::insert(const_iterator pos, const value_type& value) { TINYSTL_DEBUG(pos >= begin() && pos <= end()); iterator xpos = const_cast<iterator>(pos); const size_type n = xpos - begin_; if (end_ != cap_ && xpos == end_) { data_allocator::construct(tinystl::address_of(*end_), value); ++end_; } else if (end_ != cap_) { auto new_end = end_; data_allocator::construct(tinystl::address_of(*end_), *(end_ - 1)); ++new_end; auto value_copy = value; tinystl::copy_backward(xpos, end_ - 1, end_); *xpos = tinystl::move(value_copy); end_ = new_end; } else { reallocate_insert(xpos, value); } return begin() + n; }
|
erase
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
template <class T> typename vector<T>::iterator vector<T>::erase(const_iterator pos) { TINYSTL_DEBUG(pos >= begin() && pos < end()); iterator xpos = begin_ + (pos - begin()); tinystl::move(xpos + 1, end_, xpos); data_allocator::destroy(end_ - 1); --end_; return xpos; }
template <class T> typename vector<T>::iterator vector<T>::erase(const_iterator first, const_iterator last) { TINYSTL_DEBUG(first >= begin() && first <= end() && !(last < first)); const auto n = first - begin(); iterator r = begin_ + (first - begin()); data_allocator::destroy(tinystl::move(r + (last - first), end_, r), end_); end_ = end_ - (last - first); return begin_ + n; }
|
这里用到了 move()
、copy_backward()
等 STL 算法部分的内容,会在 Algorithm 部分讲解。
总结
vector
是 STL 中的一种结构较为简单的容器,vector
内部维护的是一个连续的线性空间,其迭代器为普通指针类型。vector
的特点在于其是动态空间,随着元素的加人,它的内部机制会自行扩充空间以容纳新元素。为了实现这一点,vector
以 begin_
、end_
、cap_
三个迭代器分别代表 vector
目前数据的头部、尾部以及可使用空间的尾部。利用这三个迭代器可以很方便地判断当前的数据个数是否超出容量以重新分配更大的空间;
值得注意的是,vector
的重新分配空间并不是在原空间之后接续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍(或 1.5 倍)另外配置一块较大空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此,对 vector
的任何操作,一旦引起空间重新配置,指向原 vector
的所有迭代器就都失效了。