在模拟实现vector核心功能中 我经历了"内存泄漏 ------ 浅拷贝 ------ 迭代器失效 ------ 扩容策略 ------ const 正确性"等一系列只有造轮子才能遇到的坑
终于,在无数次 Segmentation fault 和 double free 之后,我完成了属于自己的动态数组
这篇文章将完整记录我的设计思路、关键代码实现、踩过的坑(都已经在代码注释中标出)以及最终的收获
1. 基础管理
- 构造:默认构造、指定大小和初始值构造、迭代器区间构造、拷贝构造
cpp
vector()
:_start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{
}
vector(int n, const T& value = T())
: _start(nullptr)//初始化列表是好习惯
, _finish(nullptr)//写了是为了更严谨,尤其是当成员在初始化列表中能直接构造时,效率更高;也避免了某些成员(如引用)无法在函数体内赋值的情况
, _endOfStorage(nullptr)//不论这里写不写 初始化列表总是要走的 写了代码健壮性更高
{
T* vec = new T[n];
for (size_t i = 0; i < n; i++)
{
vec[i] = value;
}
_start = vec;
_finish = vec + n;
_endOfStorage = vec + n;
}
template<class InputIterator>
vector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{
size_t n = last - first;
T* vec = new T[n];
size_t i = 0;
while (first != last)
{
vec[i] = *first;
first++;
i++;
}
_start = vec;
_finish = vec + n;
_endOfStorage = vec + n;
}
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{
size_t n = v.size();
T* vec = new T[n];
for (size_t i = 0; i < v.size(); i++)
{
vec[i] = v[i];
}
_start = vec;
_finish = vec + n;
_endOfStorage = vec + n;
}
vector<T>& operator= (vector<T> v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endOfStorage, v._endOfStorage);
return *this;
}
- 析构:释放内存,指针置空
cpp
~vector()
{
delete[] _start;//下面两个之所以不用delete是因为他们三指向的是同一块空间 是释放一次即可
_start = nullptr;
_finish = nullptr;
_endOfStorage = nullptr; //释放后立即置空是防止"重复释放野指针"的好习惯
}
- 赋值运算符:copy-and-swap 惯用法,深拷贝
cpp
vector<T>& operator= (vector<T> v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endOfStorage, v._endOfStorage);
return *this;
}
swap:交换内部指针
cpp
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endOfStorage, v._endOfStorage);
}
2. 容量相关
size():返回元素个数
cpp
size_t size() const
{
return _finish - _start;
}
capacity():返回当前已分配内存可容纳的元素个数
cpp
size_t capacity() const
{
return _endOfStorage - _start;
}
reserve:预留空间(只扩大),避免频繁扩容。使用 placement new 移动元素,正确释放旧内存
cpp
//void reserve(size_t n)//只放大 不缩小
//{
// if (n <= capacity()) return;
// size_t si = size();
// T* New_vec = new T[n];
// memcpy(New_vec, _start, si);//这里只进行了浅拷贝 虽然开辟了新内存可以存储成员变量 ,
// //但是一旦出现非pod数据 里面的 指针变量的指向还会是原来的空间 可能导致后面对同一块空间释放两次 导致报错
// delete[] _start;
// _start = New_vec;
// _finish = New_vec + si;
// _endOfStorage = New_vec + n;
//}
void reserve(size_t n)//只放大 不缩小
{
if (n <= capacity()) return;
size_t si = size();
//T* New_vec = new T[n];
T* New_vec = static_cast<T*>(::operator new(n * sizeof(T))); // 分配原始内存
for (size_t i = 0; i < si; ++i)
{
new (New_vec + i) T(std::move(_start[i])); // placement new 移动构造
}
// 销毁旧对象
for (size_t i = 0; i < si; ++i)
{
_start[i].~T();
}
::operator delete(_start); // 释放旧内存
_start = New_vec;
_finish = New_vec + si;
_endOfStorage = New_vec + n;
}
resize:改变元素个数,多余元素析构,不足则构造(可指定初始值)
cpp
void resize(size_t n, const T& value = T())
{
if (n < size())
{
// 析构多余元素
for (size_t i = n; i < size(); ++i)
{
_start[i].~T();
}
_finish = _start + n;
}
else if (n > size())
{
if (n > capacity())
{
reserve(n);// 扩容(会移动旧元素,更新 _start, _finish, _endOfStorage)
//有现成的扩容直接用
}
// 构造新增元素
for (size_t i = size(); i < n; ++i)
{
new (_start + i) T(value);
}
_finish = _start + n;
}
}
3. 元素访问
operator[]:下标访问,检查边界(与标准库一致)
cpp
T& operator[](size_t pos)
{
assert(pos<size());
return *(_start + pos);
}
const T& operator[](size_t pos)const
{
assert(pos < size());
return *(_start + pos);
}
4. 迭代器
begin()/end()含const:返回普通迭代器(指针)
cpp
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
const_iterator begin() const
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator end() const
{
return _finish;
}
cbegin()/cend():返回常量迭代器
cpp
const_iterator cbegin() const
{
return _start;
}
const_iterator cend() const
{
return _finish;
}
5. 修改操作
push_back:尾部插入,容量不足时扩容(翻倍策略)。使用循环赋值拷贝元素
cpp
void push_back(const T& x)
{
if (size() + 1 >= capacity())
{
size_t new_capacity = (capacity() == 0 ? 4 : capacity() * 2);
reserve(new_capacity);
}
*(_finish) = x;
_finish++;
}
pop_back:尾部删除,析构元素,大小减一
cpp
bool empty()
{
if (size() == 0) return true;
return false; //这个返回值不要忘了 条件发生与不发生
}
void pop_back()
{
assert(!empty());
--_finish;
_finish->~T();
}
insert:在迭代器位置插入元素,后移元素,扩容处理
cpp
iterator insert(iterator pos, const T& x)//既然给的是迭代器 那我传为下标不就可以了
{
if (size() + 1 >= capacity())
{
size_t new_capacity = (capacity() == 0 ? 4 : capacity() * 2);
reserve(new_capacity);
}
size_t index = pos - _start;//如果pos是指向下标为三的元素 那么-pos之后是下标为3的位置 所以符合转化要求
for (size_t i = size(); i > index; --i)//往前移动还是往后移动 要清楚
{
_start[i] = _start[i - 1];
}
*(_start+index) = x;//size不用管因为有人给计算出来
_finish++;
return _start + index;
}
erase:删除迭代器位置元素,前移后续元素,析构最后一个元素
cpp
iterator erase(iterator pos)
{
assert(!empty());
size_t index = pos - _start;
for (size_t i = index;i < size()-1; ++i)
{
_start[i] = _start[i+1];
}
_finish--;
_start[size() - 1].~T(); // 析构被"删除"的元素//优化环境下平凡析构不会被调用 断点直接失效 在非优化下则会生效
return _start + index;
}
swap:交换两个 vector 的内容 (已给出)
6. 辅助
empty:判空
cpp
bool empty()
{
if (size() == 0) return true;
return false; //这个返回值不要忘了 条件发生与不发生
}
7.其中可能会出现的疑问
1.浅拷贝与深拷贝
memcpy和循环赋值之间的区别
以下我和DeepSeek的对话 可供参考

2.理解什么是pod什么是非pod
pod即内置类型 自定义类型中不包含指针作为成员变量的 如日期类
POD类型:包括基本类型(如int、char)、数组、以及只包含POD成员的struct/class。这些类型的内存表示就是数据本身,没有内部指针指向外部资源。因此,用memcpy直接逐字节复制是安全的,复制后两个对象完全独立,不会产生资源中突。
非pod则指自定义类型中包含指针作为成员变量的 如string类
非POD类型:例如string、vector以及任何管理动态内存的类。它们的内部通常包含指针成员,指向堆上分配的资源。memcpy只复制指针值(地址),而不是资源本身,导致新l日对象共享同一块资源。析构时就会重复释放,造成崩溃。
完结-----
我的gitee仓库链接