SGISTL源码探究-vector容器(上)

来源:互联网 发布:mac foxmail 邮件备份 编辑:程序博客网 时间:2024/05/21 05:18

前言

vector容器应该算是最常用的容器之一了,它是序列式容器的一种,即元素可被排序。vector被称为动态数组,可以随着元素的加入,内部的大小自行扩充。可能你对vector的机制有所耳闻,比如每次扩充时,扩充当前容量的2倍等。接下来,我们就进入到它的源码来看看vector是如何实现的。我们将它分为四部分进行分析,vector的迭代器,vector的内存分配及构造,vector的内存控制以及一些常用操作

vector的迭代器

首先vector的迭代器是原生指针,可以根据traits技法来获取到它的相应型别。当迭代器是原生指针时,我们无法利用模板的参数推导机制获取到它的相应型别,比如value_type,而STL要求每一个迭代器都应该提供这5个内嵌相应型别,所以利用了模板的偏特化,针对原生指针。这点我们在讲解traits这一小节中已经分析过了。
vector里面内置了3个迭代器,分别是startfinishend_of_storage。这三者的作用分别是:

  • start:指向vector使用的空间的头部
  • finish:指向vector使用的空间的尾部
  • end_of_storage:代表vector可用空间的尾部

vector的空间分配及元素操作等,跟这三个迭代器的关系很密切。关于vector迭代器的定义,我们直接看源码。

template <class T, class Alloc = alloc>class vector {public:  typedef T value_type;  typedef value_type* pointer;  typedef const value_type* const_pointer;  //定义迭代器,就是一个普通的指针  //而random_access_iterator_tag可以提供的操作和普通操作一样  typedef value_type* iterator;  typedef const value_type* const_iterator;  typedef value_type& reference;  typedef const value_type& const_reference;  typedef size_t size_type;  typedef ptrdiff_t difference_type;  ......protected:  typedef simple_alloc<value_type, Alloc> data_allocator;  iterator start;  iterator finish;  iterator end_of_storage;  ......public:  iterator begin() { return start; }  const_iterator begin() const { return start; }  iterator end() { return finish; }  const_iterator end() const { return finish; }  ......

在这段代码中,除了看到迭代器的声明之外,还看到了关于vector自己的空间配置器,指定value_type为申请单位。
最后还看到了我们再熟悉不过的begin()end()这两个返回指向容器首、尾的迭代器的函数。在实现中,它们只是简单的返回了vector内部的迭代器。

vector内存分配及构造

这部分无疑是vector最核心的部分。要想了解vector内部是如何控制它的空间的,我们先从它的众多构造函数下手。

vector的构造函数

vector有以下几种构造函数:

vector() : start(0), finish(0), end_of_storage(0) {}vector(size_type n, const T& value) { fill_initialize(n, value); }vector(int n, const T& value) { fill_initialize(n, value); }vector(long n, const T& value) { fill_initialize(n, value); }explicit vector(size_type n) { fill_initialize(n, T()); }vector(const vector<T, Alloc>& x) {  start = allocate_and_copy(x.end() - x.begin(), x.begin(), x.end());  finish = start + (x.end() - x.begin());  end_of_storage = finish;}#ifdef __STL_MEMBER_TEMPLATEStemplate <class InputIterator>vector(InputIterator first, InputIterator last) :  start(0), finish(0), end_of_storage(0){  range_initialize(first, last, iterator_category(first));}#else /* __STL_MEMBER_TEMPLATES */vector(const_iterator first, const_iterator last) {  size_type n = 0;  distance(first, last, n);  start = allocate_and_copy(n, first, last);  finish = start + n;  end_of_storage = finish;}

接下来我们依次来分析它们的源码。

vector() : start(0), finish(0), end_of_storage(0)

第一种构造函数即什么参数都不传,这个时候vector的容量为0。使用vector<string> t时调用。

vector(int n, const T& value)

这种构造函数与vector(int n, const T& value)
vector(long n, const T& value)类似,只是因为n的类型不同重载了而已,但是它们内部调用的都是fill_initialize(n, value)。当我们平时使用vector<string> v(5, "123")这样的操作时就会调用该类构造函数。

explicit vector(size_type n)

这类构造函数只分配n个T的数量的空间,并且调用T的默认构造函数进行初始化。至于explicit关键字,它是用来防止当只有一个传入参数时发生隐式转换的。比如这样的操作vector<int> v = 'a',如果没有加explicit关键字,居然会调用成功,所以explicit的重要不言而喻。

vector(const vector
/* 调用了allocate_and_copy函数分配内存及复制数据 * 并且维护这三个迭代器 */start = allocate_and_copy(x.end() - x.begin(), x.begin(), x.end());finish = start + (x.end() - x.begin());end_of_storage = finish;
vector(InputIterator first, InputIterator last)

这种构造函数是根据迭代器firstlast进行范围构造的。SGISTL中提供了两个版本,根据__STL_MEMBER_TEMPLATES是否被宏定义了进行条件编译。

内存分配及初始化

以上便是vector的构造函数,里面调用了fill_initialize以及allocate_and_copy还有range_initialize这几个函数,我们来看看它们到底干了什么。

fill_initialize
/* 调用了allocate_and_fill函数来分配及初始化空间 * 维护了vector的三个迭代器 */void fill_initialize(size_type n, const T& value) {  start = allocate_and_fill(n, value);  finish = start + n;  end_of_storage = finish;}
allocate_and_fill
iterator allocate_and_fill(size_type n, const T& x) {  //使用vector专属的空间配置器分配内存  iterator result = data_allocator::allocate(n);  /* __STL_TRY...__STL_UNWIND类似异常处理的try...catch语句块   * 这段代码的大意就是,初始化allocate分配的未初始化空间   * 如果失败了,则将分配的内存回收,防止内存泄露   */  __STL_TRY {    uninitialized_fill_n(result, n, x);    return result;  }  __STL_UNWIND(data_allocator::deallocate(result, n));}
allocate_and_copy
/* 与allocate_and_fill十分相似 * 唯一的不同的就是uninitialized_xxxx的不同调用 */template <class ForwardIterator>iterator allocate_and_copy(size_type n,                           ForwardIterator first, ForwardIterator last) {  iterator result = data_allocator::allocate(n);  __STL_TRY {    uninitialized_copy(first, last, result);    return result;  }  __STL_UNWIND(data_allocator::deallocate(result, n));}
range_initialize

该函数根据迭代器的类型不同,重载了不同的版本。代码如下:

//针对InputIterator类型的迭代器template <class InputIterator>void range_initialize(InputIterator first, InputIterator last,                      input_iterator_tag) {  for ( ; first != last; ++first)    push_back(*first);}// This function is only called by the constructor.  We have to worry//  about resource leaks, but not about maintaining invariants.//不是很理解为什么这里需要ForwardIterator类型的//仔细检查了一下该函数内部调用的函数//InputIterator类型的应该也可以满足//不是很明白,望知道的告知,谢谢!template <class ForwardIterator>void range_initialize(ForwardIterator first, ForwardIterator last,                      forward_iterator_tag) {  size_type n = 0;  distance(first, last, n);  start = allocate_and_copy(n, first, last);  finish = start + n;  end_of_storage = finish;}

vector内存的控制

前面我们看到了vector的各种构造函数,以及如何分配内存及初始化的。
接下来,我们来分析vector的内存控制,比如当最初申请的空间满了,是如何扩充的,以及清空vector等操作。
它们主要跟push_backclear等操作有关。

我们先来看看一些查看当前容量的函数。

size_type size() const { return size_type(end() - begin()); }size_type max_size() const { return size_type(-1) / sizeof(T); }size_type capacity() const {return size_type(end_of_storage - begin()); }bool empty() const { return begin() == end(); }

max_size代表当前最多能够存储的T类型的个数。
capacity代表vector总空间的大小,目前正在使用的+预留的。
它是end_of_storagestart迭代器的距离。在初始时,end_of_storage = finish,随着push_back的调用,capacity的大小会发生改变。
这里写了一段小代码,可以帮助我们理解capacitysize的区别,以及clear函数的作用。

#include <iostream>using namespace std;#include <vector>int main(){    vector<int> a(5);    cout << "capacity: " << a.capacity() << endl;    cout << "size: " << a.size() << endl;    a.push_back(1);    cout << "capacity: " << a.capacity() << endl;    cout << "size: " << a.size() << endl;    a.clear();    cout << "capacity: " << a.capacity() << endl;    cout << "size: " << a.size() << endl;    vector<int>().swap(a);  //{  //  vector<int>temp;  //  tepm.swap(a);    //}    cout << "capacity: " << a.capacity() << endl;    cout << "size: " << a.size() << endl;    return 0;}

运行结果:

capacity: 5size: 5capacity: 10size: 6capacity: 10size: 0capacity: 0size: 0

可以看到当初始状态时,capacitysize返回的大小相等,对应到内部就是end_of_storagefinish迭代器指向的位置相同。
当添加元素之后,capacity的容量变成了原来的capacity的2倍。
当我们使用clear,只是调用了vector存储的元素的析构函数,但是vector本身的容量是没有释放的。
如果想释放内存,使用swap函数即可,原理是将原本的vector的空间换一个临时vector变量,这样临时变量离开作用域时,就会自动调用析构函数释放内存。

在下一节中,我们将正式开始分析push_back,通过它来了解上面的例子中,vector内部执行的操作。

小结

本小节中我们主要了解了vector的迭代器,以及它的空间配置器、构造函数,还有部分关于内存控制方面的,在下一小节,我们将主要针对push_back等函数的操作,将vector关于它内部空间的控制一探究竟,然后再分析部分的操作函数。

原创粉丝点击