SGISTL源码阅读九 Vector容器中
前言
在上一篇文章中我们了解了Vector
的基本结构、构造以及内存分配,但是对于Vector
的动态性并未涉及。
接下来我们继续学习vector
的一些相关操作,从中我们可以看到vector
是如何进行内存控制的
深入源码
push_back
void push_back(const T& x) {
//判断vector的使用量是否达到最大值
//如果还有多余的空间,则直接调用construct构造,并且维护vector的迭代器
if (finish != end_of_storage) {
construct(finish, x);
++finish;
}
//否则调用insert_aux
else
insert_aux(end(), x);
}
insert_aux
template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x) {
//判断vector的使用量是否达到最大值
//这段代码的作用是将传入的值x放到指定位置(position上去)
//你可能会疑惑这个if我们在push_back中不是已经判断了吗为啥还要判断一次
//因为insert_aux不仅仅只会被push_back调用
if (finish != end_of_storage) {
//finish指向的是未使用空间的头, 所以将finish上的值初始化为finish-1上的值,并讲finish++
construct(finish, *(finish - 1));
++finish;
T x_copy = x;
//将vector上的数依次往后挪,留出position位置
copy_backward(position, finish - 2, finish - 1);
//给position位置赋值
*position = x_copy;
}
//处理容量不够的情况
else {
const size_type old_size = size();
//这里处理了旧容量为0的情况
const size_type len = old_size != 0 ? 2 * old_size : 1;
//使用空间配置器重新申请一个两倍大小的空间(或者是1个)
iterator new_start = data_allocator::allocate(len);
iterator new_finish = new_start;
__STL_TRY {
//初始化操作,将原来的vector拷贝一份到新的空间上
new_finish = uninitialized_copy(start, position, new_start);
construct(new_finish, x);
++new_finish;
new_finish = uninitialized_copy(position, finish, new_finish);
}
# ifdef __STL_USE_EXCEPTIONS
catch(...) {
//处理异常,销毁拷贝过去的元素,并将空间释放
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
# endif /* __STL_USE_EXCEPTIONS */
//销毁元素,释放原来的空间并且更新vector的迭代器,扩容完成
destroy(begin(), end());
deallocate();
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
在insert_aux
中我们清楚地看到了vector
对容量的管理,以及那三个迭代器的作用。
只要容量不够用了,我们就申请一个原vector
2倍大小的空间,将原vector
的数据拷贝到新的空间后释放,最后维护vector
的三个迭代器。
但是这也有可能会造成迭代器失效的问题,比如说我们定义了一个迭代器指向vector
中的元素,vector
在我们不知晓的情况下进行了扩容,原来的空间被释放了,迭代器就指向了一个非法地址。
insert
insert
有以下几个版本
//指定位置插入一个值为x的元素
iterator insert(iterator position, const T& x) {
size_type n = position - begin();
//如果容量够用且所插入的位置是最后一个
if (finish != end_of_storage && position == end()) {
construct(finish, x);
++finish;
}
//如果容量不够用或者插入的位置不是最后一个(insert_aux中处理了这种情况)
else
insert_aux(position, x);
return begin() + n;
}
//仅仅指定了位置,则插入一个指定类型的默认构造对象
iterator insert(iterator position) { return insert(position, T()); }
#ifdef __STL_MEMBER_TEMPLATES
//范围插入在指定位置插入一段由first和last迭代器指定范围的数据
template <class InputIterator>
void insert(iterator position, InputIterator first, InputIterator last) {
//调用range_insert(之后也会讲到)
range_insert(position, first, last, iterator_category(first));
}
#else /* __STL_MEMBER_TEMPLATES */
//同为范围插入
void insert(iterator position,
const_iterator first, const_iterator last);
#endif /* __STL_MEMBER_TEMPLATES */
//指定位置插入n个值为x的元素
void insert (iterator pos, size_type n, const T& x);
//重载版本
void insert (iterator pos, int n, const T& x) {
insert(pos, (size_type) n, x);
}
//重载版本
void insert (iterator pos, long n, const T& x) {
insert(pos, (size_type) n, x);
}
总的来说insert操作分为三种
- 指定位置插入一个元素(在上面的源码中我们介绍过了)
- 指定位置插入n个元素
- 以迭代器指定范围插入
指定位置插入n个元素
代码和迭代器之情范围插入极其类似,我们只分析其中之一。
以迭代器指定范围插入
根据条件编译分不同的函数,但是实现都是一样的,range_insert
和下面insert
的代码一致
template <class T, class Alloc>
void vector<T, Alloc>::insert(iterator position,
const_iterator first,
const_iterator last) {
if (first != last) {
size_type n = 0;
//调用distance求出迭代器指定范围元素个数
distance(first, last, n);
//如果剩余容量足够,则将元素插入并维护vector的迭代器
if (size_type(end_of_storage - finish) >= n) {
const size_type elems_after = finish - position;
iterator old_finish = finish;
if (elems_after > n) {
//初始化未初始化空间
uninitialized_copy(finish - n, finish, finish);
finish += n;
//将原vector相应位置向后移动,留出n个位置供插入
copy_backward(position, old_finish - n, old_finish);
//插入元素
copy(first, last, position);
}
//如果position的位置刚好在原vector的末尾,则直接插入
else {
uninitialized_copy(first + elems_after, last, finish);
finish += n - elems_after;
uninitialized_copy(position, old_finish, finish);
finish += elems_after;
copy(first, first + elems_after, position);
}
}
//如果剩余容量不足的情况
else {
const size_type old_size = size();
//申请至少原vector容量的两倍空间(如果n的值大于old_size则会超过两倍)
const size_type len = old_size + max(old_size, n);
iterator new_start = data_allocator::allocate(len);
iterator new_finish = new_start;
__STL_TRY {
//将原vector头至插入点position前的元素复制到新空间
new_finish = uninitialized_copy(start, position, new_start);
//将需要插入的元素复制到新空间
new_finish = uninitialized_copy(first, last, new_finish);
//将原vector剩余元素赋值到新空间
new_finish = uninitialized_copy(position, finish, new_finish);
}
# ifdef __STL_USE_EXCEPTIONS
catch(...) {
//以下是处理异常
//将复制了的元素销毁并且释放空间
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
# endif /* __STL_USE_EXCEPTIONS */
//将原vector的元素销毁并释放空间,并且维护vector的三个迭代器,完成扩容
destroy(start, finish);
deallocate();
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
}
总结
通过以上的源码学习,我们了解到了vector
的内存控制机制,并且讲到了vector的插入操作。
可见vector
的动态性其实也只是一个假象,需要将原来的空间释放并且申请一块更大的空间。
之后我们将继续学习vector
的相关操作。