《STL 源码剖析》 vector 实现原理
摘抄于 《STL 源码剖析》 4.2
vector概述
vector 和 array 非常相似。两者的唯一差别在于空间的运用的灵活性。
array是静态空间,一旦配置就不能改变。
如果要改变需要 用户自己操作:配置一个新空间将元素从旧地址搬到新地址,把原来的空间释放。
vector 是 动态控件,随着元素的加入,内部机制会自行扩充空间以容纳新元素。
vector的实现技术,关键 在于 其对大小的控制 以及 重新配置时的数据移动效率。
vector迭代器
因为 vector 维护的是一个连续线性空间,因此 普通指针可以作为vector的迭代器,满足迭代器的必要条件。
vector 提供的是 Random Access Iterators
Random Access Iterators 是迭代器的五大分类之一
vector 数据结构
class vector{
...
protected:
iterator start; // 指向使用连续空间的第一个元素的位置
iterator finish;// 指向使用连续空间的最后一个元素的下一个位置
iterator end_of_storage;// 指向整个连续空间的尾端
...
};
通过 start,finish,end_of_storage 三个迭代器 可以很简单的提供大部分功能:大小、是否为空、[ ]、前端元素、后端元素:
class vector{
...
public:
iterator begin() { return start; }
iterator end() { return finish; }
size_type size() const { return size_type(end() - begin()); }
size_type capacity() const{
return size_type(end_of_storage - begin());
}
bool empty() const { return begin() == end(); }
reference operator[](size_type n) { return *(begin() + n); }
reference front() { return *begin(); }
reference back() { return *(end() - 1); }
...
};
内存示意图:
vector 构造与内存管理
- 构造
// 指定大小和初值的构造函数
vector(size_type n,const T& value){ fill_initialize(n,value); }
void fill_initialize(size_type n,const T& value)
{
start = allocate_and_fill(n,value);
finish = start + n;
end_of_storage = finish;
}
iterator allocate_and_fill(size_type n,const T& x)
{
iterator result = data_allocator::allocate(n); // 分配 n个 元素的空间
uinitialized_fill_n(result,n,x); // 给 [result,result+n) 设置初始值 x
return result;
}
- push_back 原理
void push_back(const T& x)
{
if(finish != end_of_storage) // 说明还有备用空间
{
construct(finish, x); // constuct(T* p, const T& x); 构造函数
++finish;
}
else
insert_aux(end(), x);// vector 成员函数
}
// 实现 空间资源的重新分配,得到更大的空间
template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x)
{
if(finish != end_of_storage) // 还有备用空间 - 上面明明判断过了,这边加上去的理由应该是 防止其他情况 处理吧
{
construct(finish, *(finish - 1));
++finish;
T x_copy = x;
copy_backward(position, finish - 2, finish - 1);
*position = x_copy;
}
else
{
const size_type old_size = size();
const size_type len = old_size != 0 ? 2 * old_size : 1;// 这里实现了 新空间 大小翻倍
iterator new_start = data_allocator::allocate(len); // 申请新 空间
iterator new_finish = new_start;
try
{
// 将原本的内容拷贝 到新的 vector
new_finish = uninitialized_copy(start, position, new_start); // 将 [start,position) 的内容 拷贝 到 new_start为起点的 vector中
// 为新元素 设定 初值 x
construct(new_finish, x);
++new_finish;
// 然后 再把 后半段 也拷过来
new_finish = uninitialized_copy(position, finish, new_finish); // 将 [position,finish) 的内容 拷贝 到 new_finish为起点的 vector中
}
catch(...)
{
// 执行失败则滚回
destroy(nwe_start, new_finish); // 析构
data_allocator::deallocate(new_start, len); // 释放申请的空间
throw;
}
// 析构并释放原来的vector
destroy(begin(), end());
dealloacte(); // 成员函数
// 将迭代器指向新的vector
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
这样就有 一些 性能消耗的 问题存在
vector 基本操作实现原理
1.pop_back
功能:剔除尾端元素 ,实现上 就是 将尾端finish往前移,将原本的尾端元素释放。
void pop_back()
{
--finish;
destroy(finish);
}
2.erase
功能1:清除 [first,last)的元素
实现上,就是将 [ last,finish)的元素 向前拷贝。将其从first开始放置
然后再 销毁 尾部剩余的元素,更新finish
iterator erase(iterator first, iterator last)
{
iterator i = copy(last,finish,first);// 将[last,finish)的元素拷贝到 first为起点的内存空间,返回 拷贝最后一个元素的位置
destroy(i, finish); // 销毁 i 到 finish 的元素
finish = finish - (last - first);// 更新finish
return first;
}
功能2:清除某个位置的元素
实现上,就是将该位置后面的元素全部往前移动( 和上面类似)
iterator erase(iterator position)
{
if(positio + 1 != end())
{
copy(position+1,finish,position);
}
--finish;
destroy(finish);
return position;
}
可以看出 erase 清除的性能消耗 在于 copy 函数的使用,在cppreference中:https://zh.cppreference.com/w/cpp/container/vector/erase 提到了其复杂度:
3.clear
功能:清除全部
实现上就是套用 erase
void clear()
{
erase(begin(), end());
}
4.insert
功能之一:从position开始 插入n个元素,元素初始值为 x
实现上:
1)插入的元素数量 小于等于 备用空间时
A. 插入的元素个数n < [position,finish) 的元素个数 elems_after
-
a.先将 末尾的n个现有元素往后 拷贝 即 [finish-n, finish) 拷贝到 [finish,finish+n)
-
b.更新尾部 finish = finish + n
-
c.再将剩下的 position到 刚刚finish-n 剩余的元素也往后拷贝即 [position,old_finish-n) 拷贝到 以old_finish起点的空间
-
d.将[position, position+n) 填充x值
B.插入的元素个数n >= [position,finish) 的元素个数elems_after
-
a.先在 尾部 填充 多出来的元素个数 x (多出来的元素个数指 n - elems_after)
-
b.更新尾部 finish
-
c.再将[position,old_finish) 往后拷贝,以 新的finish为起点的空间
-
d.更新尾部 finish
-
e.修改 [position,old_finish) 值为 x
2)插入的元素数量 大于 备用空间 :分成三段拼接
-
a.申请空间 len = size + n
-
b.将旧vector 的 [start,position) 拷贝到 new_start为起点的空间,得到new_finish
-
c.在 new_finish 处 填充n个x ,更新 new_finish
-
d.再将旧的vector 的 [position,finish) 拷贝 到 new_finsh为起点的空间
-
e.销毁旧vector
template <class T, class Alloc>
void vector<T, Alloc>::insert(iterator position, size_type n, const T& x)
{
if(n!=0)
{
if(size_type(end_of_storage - finish) >=n) // 场景1)
{
T x_copy = x;
const size_type elems_after = finish - position; //position后面的元素个数
iterator old_finish = finish;
if(elems_after > n) // 场景1) 中的 场景A.
{
uninitialized_copy(finish - n, finish, finish); // A.a
finish += n;
copy_backward(position, old_finish - n, old_finish); // A.c
fill(position, position + n, x_copy); // A.d
}
else // 场景1) 中的 场景B.
{
uninitialized_fill_n(finish, n - elems_after, x_copy); // A.a
finish += n - elems_after;
uninitialized_copy(position, old_finish, finish); // A.c
finish += elems_after;
fill(position, old_finish, x_copy); // A.e
}
}
else // 场景2)
{
const size_type old_size = size();
const size_type len = old_size + max(old_size, n); // 备用空间的翻倍
iterator new_start = data_allocator::allocate(len); // 分配空间
iterator new_finish = new_start;
try // 异常情况
{
new_finish = uninitialized_copy(start, position, new_start); // 2).b
new_finish = uninitialized_fill_n(new_finish, n, x); // 2).c
new_finish = uninitialized_copy(position, finish, new_finish); // 2).d
}
catch(...)
{
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
// 2).e
destroy(start, finish);
deallocate();
// 更新游标
start = new_satrt;
finish = new_finish;
end_of_storage = new_start + len;
}
}
}
场景1).A 图:
场景1).B 图:
场景2) 图:
问题思考
1.insert中场景1).A 为什么需要 分成多个步骤执行?不能直接 将position之后的数据往后移动吗?
自我理解:
- uninitialized_copy 作用是,将数据拷贝到未初始化的区域,因此 在步骤a中,只能先拷贝到 备用空间。
- 具体内部的实现,可能 效率会比 直接将数据往后移动 高
场景1).B.a 处理上 应该也是这样的考虑。