前言
vector容器应该算是最常用的容器之一了,它是序列式容器的一种,即元素可被排序。vector被称为动态数组,可以随着元素的加入,内部的大小自行扩充。可能你对vector的机制有所耳闻,比如每次扩充时,扩充当前容量的2倍等。接下来,我们就进入到它的源码来看看vector是如何实现的。我们将它分为四部分进行分析,vector的迭代器,vector的内存分配及构造,vector的内存控制以及一些常用操作
vector的迭代器
首先vector的迭代器是原生指针,可以根据traits技法来获取到它的相应型别。当迭代器是原生指针时,我们无法利用模板的参数推导机制获取到它的相应型别,比如value_type,而STL要求每一个迭代器都应该提供这5个内嵌相应型别,所以利用了模板的偏特化,针对原生指针。这点我们在讲解traits这一小节中已经分析过了。
vector里面内置了3个迭代器,分别是start、finish、end_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_TEMPLATES
template <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)
这种构造函数是根据迭代器first和last进行范围构造的。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_back、clear等操作有关。
我们先来看看一些查看当前容量的函数。
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_storage与start迭代器的距离。在初始时,end_of_storage = finish,随着push_back的调用,capacity的大小会发生改变。
这里写了一段小代码,可以帮助我们理解capacity和size的区别,以及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: 5
size: 5
capacity: 10
size: 6
capacity: 10
size: 0
capacity: 0
size: 0
可以看到当初始状态时,capacity和size返回的大小相等,对应到内部就是end_of_storage和finish迭代器指向的位置相同。
当添加元素之后,capacity的容量变成了原来的capacity的2倍。
当我们使用clear,只是调用了vector存储的元素的析构函数,但是vector本身的容量是没有释放的。
如果想释放内存,使用swap函数即可,原理是将原本的vector的空间换一个临时vector变量,这样临时变量离开作用域时,就会自动调用析构函数释放内存。
在下一节中,我们将正式开始分析push_back,通过它来了解上面的例子中,vector内部执行的操作。
小结
本小节中我们主要了解了vector的迭代器,以及它的空间配置器、构造函数,还有部分关于内存控制方面的,在下一小节,我们将主要针对push_back等函数的操作,将vector关于它内部空间的控制一探究竟,然后再分析部分的操作函数。

本文深入探讨SGISTL中vector容器的源码,包括其迭代器类型、内存分配与构造函数的详细实现。文章分析了vector的构造方式,如无参构造、指定大小和值构造、拷贝构造以及迭代器范围构造,并讨论了内存分配、初始化以及内存控制的关键函数。
365

被折叠的 条评论
为什么被折叠?



