Std::Vector详解与实现
std::vector是一种动态数组容器,是存放在连续的内存空间,并且可以自动扩容,下面详细介绍一下std::vector的底层原理
核心机制
std::vector的核心机制主要有以下四个,分别是
- 连续存储
std::vector内部会维护一块连续的动态内存空间,将元素存储在其中,可以像数组一样通过下标快速访问元素(时间复杂度为O(1))
-
数组内部容量与大小分离
数组内部有两个表示大小的成员变量分别是
size与capacity,其中size表示当前实际存储的元素数量,capacity表示当前分配的内存可以容纳的最大元素数量,当两者相等时,说明当前内存空间已满,会触发重新分配内存空间,即扩容 -
拥有一个动态扩容机制
当需要插入元素而空间不足时,会自动分配一块更大的内存(通常是当前容量的 1.5 倍或 2 倍),具体实现原理后面章节会详细介绍
-
利用指针管理内存
内部通过三个指针管理内存,分别是:
– start_:指向内存起始位置的指针
– end_:指向当前最后一个元素的后一个位置的指针
– end_of_storage_:指向内存块的末尾指针
与array的对比
| 特性 | 数组(Array) | 向量(Vector) |
|---|---|---|
| 大小 | 固定大小(编译时或运行时) | 动态可变大小 |
| 内存管理 | 手动管理(需要预留足够空间) | 自动管理(自动扩展或收缩) |
| 支持的操作 | 限制较多 | 丰富的成员函数和操作 |
| 安全性 | 较低(易发生缓冲区溢出) | 较高(通过成员函数进行边界检查) |
| 与 STL 算法的兼容性 | 低 | 高 |
底层原理与实现
类型别名与成员变量
template<typename T, typename Alloc = MyAlloc<T>>
class vector {
public:
using value_type = T;
using allocator_type = Alloc;
using size_type = size_t;
using difference_type = ptrdiff_t;
using reference = value_type &;
using const_reference = const value_type &;
using pointer = typename Alloc::pointer;
using const_pointer = typename Alloc::const_pointer;
using iterator = pointer;
using const_iterator = const_pointer;
using reverse_iterator = my::reverse_iterator<iterator>;
using const_reverse_iterator = my::reverse_iterator<const_iterator>;
/// 定义成员变量
pointer start_; /// 指向开头
pointer finish_; /// 指向最后一个有效元素的下一位
pointer end_of_storage_; /// 指向分配空间的最后
Alloc alloc_;
}
首先定义了完整的类型别名以及所需要的三个指针,以此来管理分配的内存地址,内存分配器可以使用自己自定义的内存分配器,但是我个人觉得一般情况下使用标准的内存分配器效果更好,看个人考量吧。
构造函数与析构
我们需要提供几种典型的构造情形,比如默认构造、单参构造、通过初始化列表来构造、迭代器范围构造以及拷贝构造和移动构造等,下面一一给出这几种典型的情况,并且解释其原理
默认构造
默认构造函数,就是最简单的方式,利用初始化列表的方式给三个指针初始化为nullptr就行
vector() : start_(nullptr), finish_(nullptr), end_of_storage_(nullptr) {}
单参构造
为了防止隐式类型转换,我们通常在单参构造时添加 explicit关键字,必须显示指定其具体类型,如下所示。
explicit vector(size_type n) {
create_storage_(n);
default_initialize_(n);
}
这里在单参构造大小为n的数组时,起始是调用了底层的私有接口函数,分别开辟了内存地址空间以及在空间上构造对象,这里介绍这两个私有接口
create_storage_
这个接口的主要作用就是分配相应的内存空间,更新三个成员变量的值
void create_storage_(size_type n) {
/// 分配空间
start_ = alloc_.allocate(n); /// 这是底层内存分配器完成的工作
finish_ = start_;
end_of_storage_ = start_ + n;
}
这里仅是分配了对应的内存空间,并未构造对象
default_initialize_
由于底层构造对象的函数都是可以复用的,所以这里调用了uninitalized_default_n_a,在first开始的内存空间中,构造对象
void default_initialize_(size_type n) {
finish_ = uninitalized_default_n_a(start_, n, alloc_);
}
uninitalized_default_n_a
使用默认构造函数构造 n 个对象到 first 开始的内存空间, 构造过程中失败,就销毁 [first, cur) 区间—>保证异常安全
template <typename ForwardIterator, typename Size, typename Alloc>
ForwardIterator uninitalized_default_n_a(ForwardIterator first, Size n, Alloc& alloc) {
ForwardIterator cur = first; /// 记录构造进度
try{
for(; n > 0; --n, ++cur) {
alloc.construct(std::addressof(*cur));
}
return cur; /// 返回下一个未初始化的位置
} catch (...) {
destroy_(first, cur, alloc);
throw;
}
}
这里在构造失败之后,销毁已经构造的对象函数 destroy_如下
destroy_
对 [first, last) 区间的对象调用 destroy,对 [first, last) 区间的对象调用 destroy
template<typename InputIterator, typename Alloc>
void destroy_(InputIterator first, InputIterator last, Alloc &alloc) {
for (; first != last; ++first) {
std::allocator_traits<Alloc>::destroy(alloc, std::addressof(*first));
}
}
构造n个value
其实这些都是提供给外部的接口,内部实现都是去调用底层的私有实现方法
vector(size_type n, const T &value) : vector() {
/// 都去调用底层实现
create_storage_(n);
fill_initialize_(n, value);
}
fill_initialize_
构造n个value值的对象
void fill_initialize_(size_type n, const T &value) {
finish_ = uninitialized_fill_n_a(start_, n, value, alloc_); /// 调用底层的分配方法
}
uninitialized_fill_n_a
在[first, first + n)范围内,用value 拷贝构造 n 个对象(未初始化空间),使用自定义分配器 alloc -> 返回最后一个构造后的位置(cur),如果构造失败,则析构刚刚创建过的对象,并且释放内存、抛出异常
template<typename ForwardIterator, typename Size, typename T, typename Alloc>
ForwardIterator uninitialized_fill_n_a(ForwardIterator first, Size n, const T &value, Alloc &alloc) {
ForwardIterator cur = first; /// 记录当前构造进度位置,用于异常时回滚
try {
for (;n > 0; --n, ++cur) {
/// 调用分配构造函数,用指定值构造对象
alloc.construct(std::addressof(*cur), value);
}
return cur; /// 返回下一个未初始化的位置
} catch (...) {
/// 析构已经成功构造的 [first, cur) 区间
destroy_(first, cur, alloc);
throw;
}
}
使用 initializer_list 构造
std::vector还提供了初始化列表的构造方式,用户在定义时可以通过以下方式构造vector
std::vector<int> vec = {1, 2, 3, 5};
所以这里添加这种方式构造的实现
vector(std::initializer_list<T> ilist) {
create_storage_(ilist.size());
finish_ = uninitialized_copy_a(ilist.begin(), ilist.end(), start_, alloc_);
}
第一步分配内存空间就不多说了,都是一样的,这里介绍一下uninitialized_copy_a
uninitialized_copy_a
将 [first, last) 区间元素拷贝到 result 开始的未初始化区域==>等价于 std::uninitialized_copy, 在 vector::insert()、realloc() 时,将旧数据复制到新内存中就会用到这个函数
template <typename InputIterator, typename ForwordIterator, typename Alloc>
ForwordIterator uninitialized_copy_a(InputIterator first, InputIterator last, ForwordIterator result, Alloc& alloc) {
ForwordIterator cur = result;
try {
for (; first != last; ++first, ++cur) {
alloc.construct(std::addressof(*cur), *first);
}
return cur;
} catch(...) {
destroy_(result, cur, alloc);
throw ;
}
}
initializer_list 赋值
vector &operator=(std::initializer_list<T> ilist) {
assign(ilist.begin(), ilist.end());
return *this;
}
assign
void assign(size_type n, const value_type &value) {
fill_assign_(n, value);
}
填充n个value
fill_assign_
这里涉及到将容器中的内容替换为n个value的拷贝,所以需要考虑三种不同的情况
- 如果n大于当前容器的容量大小,创建一个新容器并且交换内容
- 如果n大于当前大小,但是小于容量,会填充现有元素并添加新元素
- 如果n小于当前大小,缩小容器的大小
void fill_assign_(size_type n, const value_type &value) {
if (n > capacity()) {
vector temp(n, value);
this->swap(temp);
} else if (n > size()) {
std::fill(start_, finish_, value);
finish_ = uninitialized_fill_n_a(finish_, n - size(), value, alloc_);
} else if (n < size()) {
std::fill(start_, start_ + n, value);
erase_at_end_(start_ + n);
} else {
erase_at_end_(start_ + n);
}
}
那在这里接着介绍一下其中用到的各种接口
swap
即交换两个空间,相信大家一看代码就懂
void swap(vector &other) {
my::swap(start_, other.start_);
my::swap(finish_, other.finish_);
my::swap(end_of_storage_, other.end_of_storage_);
}
erase_at_end_
删除指定位置之后的元素
void erase_at_end_(pointer pos) {
if (finish_ > pos) {
destroy_(pos, finish_, alloc_);
finish_ = pos;
}
}
通过迭代器范围构造
输入迭代器范围区间[first, last),利用这个迭代器这个范围内的元素构造数组
template<typename InputIt>
vector(InputIt first, InputIt last) {
size_type n = std::distance(first, last);
create_storage_(n);
finish_ = uninitialized_copy_a(first, last, start_, alloc_);
}
原理都是一样的,先分配对应大小的内存空间,然后构造对象,只是不同的构造函数构造对象的方式些许不同。
拷贝构造
vector(const vector &other) {
create_storage_(other.size());
finish_ = uninitialized_copy_a(other.start_, other.finish_, start_, alloc_);
}
移动构造
将另一个vector资源移动到当前vector之后,需要将另一个vector置空
vector(vector &&other) noexcept
: start_(other.start_), finish_(other.finish_), end_of_storage_(other.end_of_storage_) {
other.start_ = other.finish_ = other.end_of_storage_ = nullptr;
}
赋值运算符
拷贝赋值
两个数组进行赋值操作时,最核心的一点就是检查两个数组的空间容量以及大小是否普配,需要更新相关的参数
如果用来赋值的数组的数量大于当前被赋值数组的长度,需要新开辟空间;否则就需要删除多余的元素,减少空间
vector &operator=(const vector &other) {
/// 自赋值检查
if (this != &other) {
const size_type other_size = other.size();
if (other_size > capacity()) {
/// 用来赋值的数组的数量大于当前被赋值数组的长度,需要新开辟空间
pointer temp = allocate_and_copy_(other_size, other.start_, other.finish_);
destroy_(start_, finish_, alloc_);
destroy_storage_();
start_ = temp;
end_of_storage_ = start_ + other_size;
} else if (size() >= other.size()) {
/// 将赋值的数组拷贝到被赋值的数组的对应位置
pointer new_finish = std::copy(other.start_, other.finish_, start_);
/// 析构对于的对象
destroy_(new_finish, finish_, alloc_);
} else {
std::copy(other.start_, other.start_ + size(), start_);
uninitialized_copy_a(other.start_ + size(), other.finish_, finish_, alloc_);
}
finish_ = start_ + other_size;
}
return *this;
}
移动赋值
vector &operator=(vector &&other) noexcept {
if (this != &other) {
destroy_(start_, finish_, alloc_);
destroy_storage_();
/// 更新成员变量
start_ = other.start_;
finish_ = other.finish_;
end_of_storage_ = other.end_of_storage_;
/// 将other的成员变量都置为nullptr
other.start_ = other.finish_ = other.end_of_storage_ = nullptr;
}
return *this;
}
析构
析构需要析构所有的对象,并且释放内存
~vector() {
destroy_(start_, finish_, alloc_);
destroy_storage_();
}
void destroy_storage_() {
/// 释放空间
alloc_.deallocate(start_, end_of_storage_ - start_);
/// 将指针全部置为nullptr
start_ = finish_ = end_of_storage_ = nullptr;
}
迭代器相关函数
获取开始迭代器与尾后迭代器
非常量版本
iterator begin() {
return start_;
}
iterator end() {
return finish_;
}
const版本
const_iterator begin() const {
return start_;
}
const_iterator cbegin() const {
return const_iterator(start_);
}
const_iterator end() const {
return finish_;
}
const_iterator cend() const {
return const_iterator(finish_);
}
反向迭代器
reverse_iterator rbegin() {
return reverse_iterator(finish_);
}
reverse_iterator rend() {
return reverse_iterator(start_);
}
const_reverse_iterator rbegin() const {
return reverse_iterator(finish_);
}
const_reverse_iterator rend() const {
return reverse_iterator(start_);
}
接口方法
获取大小
分别提供了获取当前实际的元素大小与容量大小
size_type size() const { return finish_ - start_; }
size_type capacity() const { return end_of_storage_ - start_; }
bool empty() const { return start_ == finish_; }
size_type max_size() const { return alloc_.max_size(); }
[]与at()
[]不带边界检查的,需要用户自己处理越界的情况,at()带边界检查
reference operator[](size_type n) {
return *(start_ + n);
}
const_reference operator[](size_type n) const {
return *(start_ + n);
}
/// at()带边界检查
reference at(size_type n) {
if (n >= size()) {
throw my::exception("my::vector<T> out of range");
}
return *(start_ + n);
}
const_reference at(size_type n) const {
if (n >= size()) {
throw my::exception("my::vector<T> out of range");
}
return *(start_ + n);
}
获取元素
分别获取数组前面和后面的元素
reference front() {
return *start_;
}
const_reference front() const {
return *start_;
}
reference back() {
if (finish_ == start_) {
throw my::exception("my::vector<T> empty");
}
return *(finish_ - 1);
}
const_reference back() const {
if (finish_ == start_) {
throw my::exception("my::vector<T> empty");
}
return *(finish_ - 1);
}
预分配内存空间
使用reverse预分配内存空间,可以减少底层频繁的扩容导致的性能开销.
分配了一个新的内存空间并且拷贝旧数据之后,需要删除旧地址空间,并且更新成员变量参数值。
其实底层在做数据的操作时,也完全可以使用移动的方式,性能更好,但是我这里全部使用的拷贝。
void reserve(size_type n) {
/// 边界检查
if (n > max_size()) {
throw my::exception("my::vector<T> too long");
}
if (n > capacity()) {
size_type old_size = size();
pointer temp = allocate_and_copy_(n, start_, finish_);
destroy_(start_, finish_, alloc_); /// 析构旧的对象
destroy_storage_(); /// 释放旧的空间
start_ = temp;
finish_ = start_ + old_size;
end_of_storage_ = start_ + n;
}
}
在预分配一个更大的空间时需要分配空间并且拷贝旧地址的数据
allocate_and_copy_
都是调用底层的私有函数,前面已经介绍过了uninitialized_copy_a
template<typename ForwordIterator>
pointer allocate_and_copy_(size_type n, ForwordIterator first, ForwordIterator last) {
pointer result = alloc_.allocate(n);
try {
uninitialized_copy_a(first, last, result, alloc_);
return result;
} catch (...) {
alloc_.deallocate(result, n);
throw;
}
}
重新分配数组大小 resize()
给数组设定一个新大小,然后将数组的大小改为这个大小,如果新大小大于原始大小,说明需要扩容,并且尾部使用传入的值进行填充,如果小于当前大小,则需要将尾部多余的元素析构。
void resize(size_type new_size, const value_type &value) {
/// 判断new_size大小
if (new_size > size()) {
fill_insert_(end(), new_size - size(), value);
} else if (new_size < size()) {
/// 将末尾size() - new_size个元素删除
erase_at_end_(start_ + new_size);
}
}
void resize(size_type n) {
resize(n, T());
}
fill_insert_
这里使用了fill_insert_,在指定范围内插入,涉及到元素的移动或者拷贝以及空间够不够用的问题,主要逻辑都在代码中,这里给出具体代码
void fill_insert_(iterator pos, size_type n, const value_type &value) {
if (n == 0) {
return;
}
if (size_type(end_of_storage_ - finish_) > n) {
/// 后面还有足够的空间容纳n个value
const size_type elems_after = finish_ - pos;
pointer old_finish = finish_;
if (elems_after > n) {
/// 如果pos后的元素比n多,则往后挪n个位置,给pos处开始留n个空间value
finish_ = uninitialized_copy_a(finish_ - n, finish_, finish_, alloc_);
std::fill_n(pos, n, value);
} else {
finish_ = uninitialized_fill_n_a(finish_, n - elems_after, value, alloc_);
/// 现在是使用的拷贝,后期优化成move
finish_ = uninitialized_copy_a(pos, old_finish, finish_, alloc_);
std::fill(pos, old_finish, value);
}
} else {
/// 如果后面的空间不够了,需要开辟新的空间
const size_type old_size = size();
const size_type elems_before = pos - begin();
const size_type new_size = old_size + my::max(old_size, n);
pointer new_start = alloc_.allocate(new_size);
pointer new_finish = new_start;
try {
uninitialized_fill_n_a(new_start + elems_before, n, value, alloc_);
/// 这里先使用copy后期优化成move()
new_finish = uninitialized_copy_a(start_, pos, new_start, alloc_);
new_finish += n;
new_finish = uninitialized_copy_a(pos, finish_, new_finish, alloc_);
} catch (...) {
if (!new_finish) {
destroy_(new_start + elems_before, new_start + elems_before + n, alloc_);
} else {
destroy_(new_start, new_finish, alloc_);
}
alloc_.deallocate(new_start, new_size);
throw;
}
destroy_(start_, finish_, alloc_);
destroy_storage_();
start_ = new_start;
finish_ = new_finish;
end_of_storage_ = start_ + new_size;
}
}
shrink_to_fit
根据实际大小调整数组容量
void shrink_to_fit() {
if (size() < capacity()) {
size_type old_size = size();
pointer temp = allocate_and_copy_(old_size, start_, finish_);
destroy_(start_, finish_, alloc_);
destroy_storage_();
start_ = temp;
finish_ = start_ + old_size;
end_of_storage_ = finish_;
}
}
元素插入删除
insert
可以在一个位置插入value,在一个位置插入n个value,这两种方式比较简单,使用了底层的fill_insert_区别就在于插入的元素数量
void insert(iterator pos, const value_type &value) {
fill_insert_(pos, 1, value); /// 在指定位置插入一个value
}
void insert(iterator pos, size_type n, const value_type &value) {
fill_insert_(pos, n, value); /// 在指定位置插入n个value
}
使用迭代器范围插入
template<typename InputIt>
void insert(iterator pos, InputIt first, InputIt last) {
size_type n = my::distance(first, last);
if (n == 0) {
return;
}
if (size_type(end_of_storage_ - finish_) >= n) {
pointer old_finish = finish_;
finish_ = uninitialized_copy_a(old_finish - n, old_finish, old_finish, alloc_);
std::copy_backward(pos, old_finish, finish_);
std::copy(first, last, pos);
} else {
size_type old_size = size();
size_type elems_before = pos - begin();
size_type new_size = old_size + my::max(old_size, n);
pointer new_start = alloc_.allocate(new_size);
pointer new_finish = new_start;
try {
new_finish = uninitialized_copy_a(start_, pos, new_start, alloc_);
new_finish = uninitialized_copy_a(first, last, new_finish, alloc_);
new_finish = uninitialized_copy_a(pos, finish_, new_finish, alloc_);
} catch (...) {
destroy_(new_start, new_finish, alloc_);
alloc_.deallocate(new_start, new_size);
throw;
}
destroy_(start_, finish_, alloc_);
destroy_storage_();
start_ = new_start;
finish_ = new_finish;
end_of_storage_ = new_start + new_size;
}
}
push_back
push_back复用insert的逻辑
void push_back(const value_type &value) {
insert(end(), value);
}
emplace_back
emplace_back直接在数组的内存空间中构造,避免了先构造再拷贝到数组中去,减少了开销
如果以前分配的空间还没有用完的情况下,可以直接在后面构造,如果以前分配的空间已经不够了,就需要重新分配空间并且插入数据
template<typename ...Args>
void emplace_back(Args &&...args) {
if (finish_ != end_of_storage_) {
/// 分配的空间还没有用完,直接进行构造
std::allocator_traits<Alloc>::construct(alloc_, finish_, my::forward<Args>(args)...);
++finish_;
} else {
realloc_insert_(end(), my::forward<Args>(args)...);
}
}
/// 支持 emplace 插入
template<typename... Args>
iterator emplace(const_iterator pos, Args &&... args) {
size_type index = pos - cbegin();
emplace_impl_(begin() + index, std::forward<Args>(args)...);
return begin() + index;
}
其中在空间不够用的情况下,使用了realloc_insert_
realloc_insert_
简单来说,这个函数的作用就是分配一个更大的空间,然后将需要构造的元素,在对应的位置构造进去,涉及到的元素的移动,其实看代码就可以发现,就没有画图,所以直接给出代码
template <typename... Args>
void realloc_insert_(iterator pos, Args&&... args) {
const size_type old_size = size();
const size_type elems_before = pos - start_;
const size_type new_capacity = old_size + my::max(old_size, size_type(1)); // grow exponentially
pointer new_start = alloc_.allocate(new_capacity);
pointer new_finish = new_start;
try {
new_finish = uninitialized_copy_a(start_, pos, new_start, alloc_);
std::allocator_traits<Alloc>::construct(alloc_, new_start + elems_before, std::forward<Args>(args)...);
new_finish = new_start + elems_before + 1;
new_finish = uninitialized_copy_a(pos, finish_, new_finish, alloc_);
} catch (...) {
destroy_(new_start, new_finish, alloc_);
alloc_.deallocate(new_start, new_capacity);
throw;
}
destroy_(start_, finish_, alloc_);
destroy_storage_();
start_ = new_start;
finish_ = new_finish;
end_of_storage_ = new_start + new_capacity;
}
emplace
数组也支持了emplace插入
template<typename... Args>
iterator emplace(const_iterator pos, Args &&... args) {
size_type index = pos - cbegin();
emplace_impl_(begin() + index, std::forward<Args>(args)...);
return begin() + index;
}
emplace_iml_
template<typename... Args>
void emplace_impl_(iterator pos, Args &&... args) {
if (finish_ != end_of_storage_) {
std::move_backward(pos, finish_, finish_ + 1);
std::allocator_traits<Alloc>::construct(alloc_, pos, std::forward<Args>(args)...);
++finish_;
} else {
realloc_emplace_(pos, std::forward<Args>(args)...);
}
}
// 空间不足的情况下的处理方案
template<typename... Args>
void realloc_emplace_(iterator pos, Args &&... args) {
size_type old_size = size();
size_type elems_before = pos - start_;
size_type new_size = old_size + my::max(old_size, size_type(1));
pointer new_start = alloc_.allocate(new_size);
pointer new_finish = new_start;
try {
new_finish = uninitialized_move_a(start_, pos, new_start, alloc_);
std::allocator_traits<Alloc>::construct(alloc_, new_start + elems_before, my::forward<Args>(args)...);
++new_finish;
new_finish = uninitialized_move_a(pos, finish_, new_start + elems_before + 1, alloc_);
} catch (...) {
destroy_(new_start, new_finish, alloc_);
alloc_.deallocate(new_start, new_size);
throw;
}
destroy_(start_, finish_, alloc_);
destroy_storage_();
start_ = new_start;
finish_ = new_finish;
end_of_storage_ = new_start + new_size;
}
删除元素
pop_back
在删除元素前,我们都需要判断数组中是否为空,否则对空数组进行删除会导致未定义的行为
void pop_back() {
if (start_ == finish_) {
throw my::exception("my::vector<T> empty");
}
--finish_;
alloc_.destroy(finish_);
}
erase
iterator erase(iterator pos) {
if (pos + 1 != finish_) {
/// 将pos后的元素都往前挪
std::copy(pos + 1, finish_, pos);
}
--finish_;
alloc_.destroy(finish_);
return pos;
}
clear
void clear() {
erase_at_end_(start_);
}
重载 ==
如果要相等必须其中的每一个元素都要相等,所以需要循环比较,这是关键
bool operator==(const vector &other) const {
if (size() != other.size()) {
return false;
}
/// 如果要相等必须其中的每一个元素都要相等,所以需要循环比较
for (size_type i = 0; i < size(); ++i) {
if (start_[i] != other.start_[i]) {
return false;
}
}
return true;
}
// 复用==
bool operator!=(const vector &other) const {
return !(*this == other);
}
