目录
[Part1. 整体架构概述](#Part1. 整体架构概述)
[Part1.1 核心成员变量](#Part1.1 核心成员变量)
[Part1.2 类型别名体系](#Part1.2 类型别名体系)
[Part1.3 自定义分配器 allocator](#Part1.3 自定义分配器 allocator)
[Part2. 构造与析构函数](#Part2. 构造与析构函数)
[Part2.1. 默认构造](#Part2.1. 默认构造)
[Part2.2. 数目初始化构造](#Part2.2. 数目初始化构造)
[Part2.3. 迭代器区间构造](#Part2.3. 迭代器区间构造)
[Part2.4. 拷贝构造 & 移动构造](#Part2.4. 拷贝构造 & 移动构造)
[Part2.5. 赋值重载](#Part2.5. 赋值重载)
[Part2.6. 析构函数](#Part2.6. 析构函数)
[Part3. 核心容量操作接口](#Part3. 核心容量操作接口)
[Part3.1. reserve](#Part3.1. reserve)
[Part3.2. resize](#Part3.2. resize)
[Part3.3. shrink_to_fit](#Part3.3. shrink_to_fit)
[Part4. 元素增删改查核心接口](#Part4. 元素增删改查核心接口)
[Part4.1. 尾插 push_back](#Part4.1. 尾插 push_back)
[Part4.2. 原位构造 emplace_back](#Part4.2. 原位构造 emplace_back)
[Part4.3. 尾删 pop_back](#Part4.3. 尾删 pop_back)
[Part4.4. 任意位置 insert 插入](#Part4.4. 任意位置 insert 插入)
[Part4.5. 任意位置 erase 删除](#Part4.5. 任意位置 erase 删除)
[Part4.6. 访问接口](#Part4.6. 访问接口)
[Part5. 结语](#Part5. 结语)
前言
最近在复习C++,复习的方式就是实现STL,结合后面异常的知识,接下来来跟随小编的视角来看看吧。
let's go!!!!!!!!
Part1. 整体架构概述
Part1.1 核心成员变量
手写Vector完全对标STL原生实现,核心依靠三个指针管控内存:
_start:指向容器内存起始位置
_end:指向有效元素末尾的下一位,用于记录当前元素个数
_end_of_storage:指向总内存容量末尾的下一位,用于记录内存总大小
三者的关系直接决定了Vector的两大核心属性:
有效元素个数:
size() = _end - _start内存容量大小:
capacity() = _end_of_storage - _start除此之外,保留STL标准的分配器成员 _alloc,实现内存分配、元素构造/销毁的解耦,符合STL容器的通用设计规范
cppT* _start; T* _end; T* _end_of_storage; Alloc _alloc;
Part1.2 类型别名体系
源码中定义了一套完整的类型别名,是所有STL容器的通用写法,目的是屏蔽底层类型差异,统一接口规范,方便泛型编程适配:
value_type:容器存储的元素类型T
pointer/const_pointer:元素指针、常量指针
reference/const_reference:元素引用、常量引用
size_type/difference_type:无符号尺寸类型、指针差值类型迭代器:原生指针实现(Vector迭代器是随机访问迭代器)
反向迭代器:基于标准库
reverse_iterator封装
cppusing value_type = T; using pointer = T*; using const_pointer = const T*; using reference = T&; using const_reference = const T&; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using iterator = T*; using const_iterator = const T*; using reverse_iterator = std::reverse_iterator<iterator>; using const_reverse_iterator = std::reverse_iterator<const_iterator>;
Part1.3 自定义分配器 allocator
源码配套实现了极简版STL分配器,核心职责分为两层:内存管理和对象生命周期管理,彻底解耦「内存开辟释放」和「对象构造销毁」,这是STL容器的核心设计思想。
allocate:仅开辟原始内存,不构造对象,底层调用
operator newdeallocate:仅释放原始内存,不析构对象,底层调用
operator deleteconstruct:在已开辟的内存上,定位new构造对象,支持可变参数、完美转发
destroy:主动调用对象析构函数,销毁对象但不释放内存
max_size:返回容器最大可容纳元素数,规避内存溢出
rebind:模板重绑定,适配STL容器嵌套场景
cpptemplate<class T> class allocator { using value_type = T; using pointer = T*; using const_pointer = const T*; using reference = T&; using const_reference = const T&; using size_type = std::size_t; using difference_type = std::ptrdiff_t; public: template<class U> struct rebind { using other = allocator<U>; }; allocator() noexcept {}; allocator(const allocator<value_type>& alloc) noexcept {}; template<class U> allocator(const allocator<U>& alloc) noexcept {}; pointer allocate(size_type n) { pointer p = static_cast<pointer>(::operator new(n * sizeof(value_type))); if (p == nullptr) { throw std::bad_alloc();//里面不能加字符串 不支持 } return p; } void deallocate(pointer p,size_type) noexcept { ::operator delete(p); } size_type max_size() const noexcept { return static_cast<size_type>(-1) / sizeof(value_type); } template<class U, class... Args> void construct(U* p, Args&&... args) { new(p) U(std::forward<Args>(args)...); } template<class U> void destroy(U* p) noexcept { p->~U(); } }; template<class T, class U> bool operator==(const allocator<T>&, const allocator<U>&) noexcept { return true; } template<class T, class U> bool operator!=(const allocator<T>&, const allocator<U>&) noexcept { return false; }
Part2. 构造与析构函数
手写Vector完整实现了STL标准的所有构造、拷贝、移动构造、赋值重载,且全部实现强异常安全,杜绝内存泄漏。
Part2.1. 默认构造
初始化三个管控指针为空,接收自定义分配器,无内存开辟,零开销初始化。
cppvector(const Alloc& alloc = Alloc()) :_start(nullptr) , _end(nullptr) , _end_of_storage(nullptr) , _alloc(alloc) { //std::cout << "调用了构造函数" << std::endl; }
Part2.2. 数目初始化构造
核心逻辑:先校验最大容量,再开辟内存,循环构造元素。关键亮点:异常回滚机制
在元素循环构造过程中,若任意一次构造抛出异常,会捕获异常、销毁已构造的所有对象、释放已开辟的内存,杜绝内存泄漏后重新抛出异常,保证操作的原子性。
cppvector(size_type n, const value_type& val = value_type(), const Alloc& alloc = Alloc()) :_start(nullptr) , _end(nullptr) , _end_of_storage(nullptr) , _alloc(alloc) { //std::cout << "调用了数目构造函数" << std::endl; if (n >= max_size()) { throw std::length_error("length too big"); } _start = _alloc.allocate(n); _end = _start + n; _end_of_storage = _start + n; size_type now = 0; try { for (now = 0; now < n; now++) { _alloc.construct(_start + now, val);//这里只能走拷贝构造 因为这是对于一个对象 也就是只有val本身 当我们使用移动构造之后 这个资源已经被移动走 从而导致后续无资源调用了 } } catch (...) { for (size_type i = 0; i < now; i++) { _alloc.destroy(_start + i); } _alloc.deallocate(_start, n); throw; } }
Part2.3. 迭代器区间构造
支持任意合法输入迭代器区间初始化,核心优化点:编译期判断移动语义安全性
通过C++17
if constexpr+is_nothrow_move_constructible_v,编译期判断元素是否有无异常移动构造:
无异常移动:使用
std::move移动构造,提升效率有异常风险:降级为拷贝构造,保证异常安全
同时配套完整的异常回滚逻辑,是高性能+高安全的实现。
cpptemplate<class InputIterator> vector(InputIterator first, InputIterator last, const Alloc& alloc = Alloc(), typename std::iterator_traits<InputIterator>::iterator_category* = nullptr) :_start(nullptr) , _end(nullptr) , _end_of_storage(nullptr) , _alloc(alloc) { //std::cout << "调用了迭代器区间构造函数" << std::endl; size_type n = static_cast<size_type>(std::distance(first, last)); _start = _alloc.allocate(n); _end = _start + n; _end_of_storage = _end; InputIterator it = first; value_type* constructed = _start; try { while (it != last) { if constexpr (std::is_nothrow_move_constructible_v<T>)//用这个的目的是 强异常保证 就是防止我们在移动的中途突发异常 导致中断 此时怎么也救不会被移动走的资源 这个函数检查的是我们移动的这个对象他的移动构造是否被声明noexcept { _alloc.construct(constructed, std::move(*it)); } else { _alloc.construct(constructed, *it); } constructed++; it++; } } catch (...) { iterator del_it = _start; while (del_it != constructed) { _alloc.destroy(del_it); } _alloc.deallocate(_start, n); throw; } }
Part2.4. 拷贝构造 & 移动构造
拷贝构造:深拷贝实现,独立开辟内存、逐元素拷贝构造,完全隔离新旧容器内存,杜绝浅拷贝问题,带异常回滚。
移动构造:noexcept修饰,直接swap接管原容器的内存资源,零拷贝、零开销,仅转移指针控制权,是C++11移动语义的核心落地。
cppvector(const vector<T>& other) :_start(nullptr) , _end(nullptr) , _end_of_storage(nullptr) , _alloc(other.get_allocator()) { //std::cout << "调用了拷贝构造函数" << std::endl; size_type n = other.size(); _start = _alloc.allocate(n); _end = _start + n; _end_of_storage = _end; iterator it = other._start; value_type* cur = _start; try { while (it != other.end()) { _alloc.construct(cur, *it); cur++; it++; } } catch (...) { iterator del_it = _start; while (del_it != cur) { _alloc.destroy(del_it); } _alloc.deallocate(_start, n); throw; } } vector(vector<T>&& other) noexcept :_start(nullptr) , _end(nullptr) , _end_of_storage(nullptr) , _alloc(std::move(other.get_allocator())) { //std::cout << "调用了移动构造函数" << std::endl; swap(other); }
Part2.5. 赋值重载
拷贝赋值:采用「先构造临时对象,再swap交换」的经典写法,代码简洁且天然保证异常安全,无需手动处理旧内存释放。
移动赋值:noexcept修饰,先判断自赋值,直接swap资源转移,高效安全。
cppvector<T>& operator=(const vector<T>& other) { //std::cout << "调用了赋值重载函数" << std::endl; vector<T> tmp = other; swap(tmp); //_alloc = other._alloc; 不一定要拷贝依照分配器的特性来看 return *this; } vector<T>& operator=(vector<T>&& other) noexcept { //std::cout << "调用了移动赋值函数" << std::endl; if (this == &other) return *this; swap(other); return *this; }
Part2.6. 析构函数
严格遵循STL逻辑:先循环销毁所有有效元素(调用析构函数),再释放整块内存,杜绝内存泄漏。全程noexcept,保证析构绝对安全。
cpp~vector() noexcept { //std::cout << "调用了析构函数" << std::endl; iterator it = begin(); while (it != end()) { _alloc.destroy(it); it++; } _alloc.deallocate(begin(), capacity()); }
Part3. 核心容量操作接口
Part3.1. reserve
核心作用:预分配内存,修改capacity,不修改size,避免频繁扩容拷贝,优化性能。
核心流程:
校验目标容量,小于当前容量直接返回
开辟新的大容量内存空间
编译期适配移动/拷贝构造,迁移旧元素
异常回滚:迁移失败则销毁新内存、杜绝泄漏
销毁旧内存元素、释放旧内存,更新管控指针
关键优化:优先使用无异常移动构造,大幅提升扩容效率。
cppvoid reserve(size_type n) { if (n <= capacity()) return; if (n >= max_size()) { throw std::length_error("length too big"); } size_type low_size = size(); pointer new_ptr = _alloc.allocate(n);//这里不需要捕获 有异常会自动跳过下面的代码 直接到下面的catch pointer constructed = new_ptr; try { iterator it = begin(); while (it != end()) { if constexpr (std::is_nothrow_move_constructible_v<T>)//if constexpr C++17 用于在编译期间判断 不满足的代码会不生成 { _alloc.construct(constructed, std::move(*it)); } else { _alloc.construct(constructed, *it); } constructed++; it++; } } catch (...) { pointer ptr = new_ptr; while (ptr != constructed) { _alloc.destroy(ptr); ptr++; } _alloc.deallocate(new_ptr, n); throw; } iterator it = begin(); while (it != end()) { _alloc.destroy(it); it++; } _alloc.deallocate(_start, capacity()); _start = new_ptr; _end = _start + low_size; _end_of_storage = _start + n; }
Part3.2. resize
同时修改size,根据新旧大小分为两种场景:
n > 当前size:扩容内存,并用默认值val填充新增位置的元素,带异常回滚
n < 当前size:直接截断尾部元素,销毁多余对象,不释放内存(capacity不变)
cppvoid resize(size_type n, const value_type& val = value_type()) { if (n > max_size()) { throw std::length_error("length too big"); } if (n == size()) return; else if (n > size()) { reserve(n); iterator it = end(); try { while (it != _start + n) { _alloc.construct(it, val); it++; } } catch (...) { iterator del_it = end(); while (del_it != it) { _alloc.destroy(del_it); del_it++; } throw; } _end = _start + n; } else { iterator final = end(); _end = _start + n; iterator it = end(); while (it != final) { _alloc.destroy(it); it++; } } }
Part3.3. shrink_to_fit
将容器容量收缩至当前有效元素大小,释放多余空闲内存。逻辑与扩容相反:开辟精准大小的新内存、迁移元素、释放旧大容量内存,实现内存极致复用。
cppvoid shrink_to_fit() { size_type low_size = size(); pointer new_ptr = _alloc.allocate(low_size); pointer constructed = new_ptr; try { iterator it = begin(); while (it != end()) { if constexpr (std::is_nothrow_move_constructible_v<T>) { _alloc.construct(constructed, std::move(*it)); } else { _alloc.construct(constructed, *it); } constructed++; it++; } } catch (...) { pointer ptr = new_ptr; while (ptr != constructed) { _alloc.destroy(ptr); ptr++; } _alloc.deallocate(new_ptr, low_size); throw; } iterator it = begin(); while (it != end()) { _alloc.destroy(it); } _alloc.deallocate(_start, capacity()); _start = new_ptr; _end = _start + low_size; _end_of_storage = _start + low_size; }
Part4. 元素增删改查核心接口
Part4.1. 尾插 push_back
完美适配左值拷贝插入、右值移动插入:
容量不足时触发扩容:空容器默认初始10容量,非空容器2倍扩容(STL经典扩容策略)
左值:拷贝构造元素,保证原数据有效
右值:移动构造元素,高效复用临时资源
cppvoid push_back(const T& val) { if (size() + 1 > capacity()) { size() == 0 ? reserve(FIRST_SIZE) : reserve(size() * 2); } _alloc.construct(_end, val); _end++; } void push_back(T&& val) { if (size() + 1 > capacity()) { size() == 0 ? reserve(FIRST_SIZE) : reserve(size() * 2); } _alloc.construct(_end, std::move(val)); _end++; }
Part4.2. 原位构造 emplace_back
C++11核心优化接口,支持参数完美转发,直接在容器内存中原位构造对象,省去「构造临时对象+拷贝/移动」的开销,是效率最高的尾插方式。
cpptemplate<class... Args> reference emplace_back(Args&&... args) { if (size() + 1 > capacity()) { size() == 0 ? reserve(FIRST_SIZE) : reserve(size() * 2); } _alloc.construct(_end, std::forward<Args>(args)...); _end++; return *(_end - 1); }
Part4.3. 尾删 pop_back
noexcept无异常操作:仅销毁尾部元素、前移_end指针,不释放内存,保留容量,方便后续复用。
cppvoid pop_back() noexcept { _alloc.destroy(_end - 1); _end--; }
Part4.4. 任意位置 insert 插入
实现了单元素左值/右值插入、批量插入三种重载,核心难点:
插入前校验扩容,重点保存偏移量:扩容会导致原迭代器失效,通过偏移量重新定位插入位置,解决迭代器失效问题
从尾部开始整体后移元素,适配移动/拷贝赋值
完成元素插入,更新有效元素个数
同时适配批量插入场景,支持一次性插入n个元素,逻辑严谨兼容。
cppiterator insert(iterator pos, const T& val) { if (size() + 1 > capacity()) { size_type offset = pos - begin(); size() == 0 ? reserve(FIRST_SIZE) : reserve(size() * 2); pos = begin() + offset;//扩容后更新 } iterator it = _end; try { while (it != pos) { if constexpr (std::is_nothrow_move_constructible_v<T>) { _alloc.construct(it, std::move(*(it - 1))); } else { _alloc.construct(it, *(it - 1)); } _alloc.destroy(it - 1); it--; } } catch (...) { throw;//无法做到强异常安全 } _alloc.construct(pos, val); _end++; return pos; } iterator insert(iterator pos, T&& val) { if (size() + 1 > capacity()) { size_type offset = pos - begin(); size() == 0 ? reserve(FIRST_SIZE) : reserve(size() * 2); pos = begin() + offset;//扩容后更新 } iterator it = _end; try { while (it != pos) { if constexpr (std::is_nothrow_move_constructible_v<T>) { _alloc.construct(it, std::move(*(it - 1))); } else { _alloc.construct(it, *(it - 1)); } _alloc.destroy(it - 1); it--; } } catch (...) { throw;//无法做到强异常安全 } try { _alloc.construct(pos, std::move(val)); } catch (...) { throw;//无法做到强异常安全 } _end++; return pos; } iterator insert(iterator pos, size_type n, const T& val)//这个没有右值版本 因为这个要构造多个对象 在构造第一个的时候直接就是没有资源了 { if (n > max_size()) { throw std::length_error("length too big"); } if (size() + n > capacity()) { size_type offset = pos - begin(); size() == 0 ? reserve(FIRST_SIZE + n) : reserve(size() * 2 + n); pos = begin() + offset; } iterator it = _end + n - 1; try { while (it != pos + n - 1) { if constexpr (std::is_nothrow_move_constructible_v<T>) { _alloc.construct(it, std::move(*(it - n))); } else { _alloc.construct(it, *(it - n)); } _alloc.destroy(it - n); it--; } } catch (...) { throw;//无法做到强异常安全 } try { size_type now = 0; for (size_type now = 0; now < n; now++) { _alloc.construct(pos + now, val); } } catch (...) { throw; } _end += n; return pos; }
Part4.5. 任意位置 erase 删除
实现单元素删除、区间删除,核心逻辑:
用后序元素覆盖待删除元素(移动赋值/拷贝赋值)
销毁尾部多余元素
更新有效元素个数,返回当前有效迭代器(缓解迭代器失效)
通过
is_nothrow_move_assignable_v判断移动赋值安全性,兼顾性能与异常安全。
cppiterator erase(iterator pos) { iterator it = pos; try { while (it != end()-1) { if constexpr (std::is_nothrow_move_assignable_v<T>) { *it=std::move(*(it + 1));//全是在已经初始化的内存上实现的 } else { *it = *(it + 1); } it++; } } catch (...) { throw;//无法做到强异常安全 } _alloc.destroy(it); _end--; return pos; } iterator erase(iterator first, iterator last) { iterator it = first; size_type n = last - first; try { while (it != end()-n) { if constexpr (std::is_nothrow_move_assignable_v<T>)//判断移动赋值是否会出异常 { *it = std::move(*(it + n)); } else { *it = *(it + n); } it++; } } catch (...) { throw;//无法做到强异常安全 } while (it != end()) { _alloc.destroy(it); it++; } _end -= n; return first; }
Part4.6. 访问接口
[]:无越界检查,高效访问,适合确定下标合法的场景
at():带越界校验,越界抛出out_of_range异常,安全优先
front/back:快速访问首尾元素
cppreference operator[](size_type n) noexcept { return *(_start + n); } const_reference operator[](size_type n) const noexcept { return *(_start + n); } reference at(size_type n) { if (n >= size()) throw std::out_of_range("vector at out of range"); return *(_start + n); } const_reference at(size_type n) const { if (n >= size()) throw std::out_of_range("vector at out of range"); return *(_start + n); } reference front() noexcept { return *_start; } const_reference front() const noexcept { return *_start; } reference back() noexcept { return *(_end - 1); } const_reference back() const noexcept { return *(_end - 1); }
Part5. 结语
这篇文章我们认识并知道了vector的实现,接下来,小编还会带来更多知识,敬请期待~
最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~
