
大家好啊,我是云泽Q,欢迎阅读我的文章,一名热爱计算机技术的在校大学生,喜欢在课余时间做一些计算机技术的总结性文章,希望我的文章能为你解答困惑~ 这篇内容还是建议衔接上一篇内容来看,如果直接看建议直接带着下面的源码看深入理解vector:模拟实现与现代C++技巧
这里实现的vector还有一个隐藏很深的bug很难发现

涉及的类型与转换逻辑 代码中传入 push_back 的是字符串字面量(如 “1111…”),其类型是 const char*;而 yunze::vector< string > 的 push_back 期望接收的是 std::string 类型的参数; 由于 std::string 类提供了一个非 explicit 的构造函数 std::string(const char*),因此编译器会自动触发隐式类型转换:将 const char* 类型的字符串字面量转换为 std::string 临时 std::string 对象,再将临时对象传入 push_back,完成元素插入。
图中插入4组数据没有问题,第5组就会出现下图的情况,上一篇vector的文章也证实了整型的扩容不会有问题

这里肯定是和扩容有关系的,插入第5组数据一定会发生扩容 这里调试时将旧空间的数据拷贝给新空间这一步是没有问题的,但是旧空间delete后新旧空间都出现问题了

这里有一个经验,若delete/free出问题了,若释放的位置没有问题,那就不是这两者导致的,就比如说这里delete的指针_start没有变化过,一直指向空间开始的位置(因为不能部分释放),那就不是delete的问题(比如说空间开少了越界了也会在delete处报错)
这里主要是浅拷贝的问题,只不过层次更深,这里memcpy出现了问题
假设调用了push_back(“hello”),这时候vector内部会经历扩容(reserve) 过程
一、先看string对象的内存结构 std::string内部不是直接存字符串,而是用指针管理动态内存(类似下面的简化版):
class string {
private:
char* _str; // 指向堆上的字符数组(比如"hello"存这里)
size_t _size; // 字符串长度
size_t _cap; // 容量
};比如string s = “hello”,内存里是这样的:
s:
_str → 堆内存地址0x100(存着'h','e','l','l','o','\0')
_size = 5
_cap = 5二、旧代码用memcpy复制的问题(浅拷贝的坑) 这里的的reserve函数里用memcpy复制旧数据到新空间:
memcpy(tmp, _start, sizeof(T) * sz); // T是string时,这里出问题memcpy的本质是 “按字节复制内存”,它不管对象内部的逻辑,只把旧对象的二进制数据原封不动搬到新空间。 当T是string时,复制后新空间的string对象会变成这样:
旧空间的string对象s_old:
_str → 0x100(堆上的"hello")
新空间的string对象s_new(memcpy复制后):
_str → 0x100(和s_old的_str完全一样!)
_size = 5(复制了s_old的_size)
_cap = 5(复制了s_old的_cap)这时候s_old和s_new的_str指针指向同一块堆内存(0x100),这就是 “浅拷贝”—— 只复制了指针,没复制指针指向的实际数据。
三、崩溃的关键步骤:旧空间释放导致新对象 “悬空” 扩容的最后一步是释放旧空间:
delete[] _start; // 释放旧空间delete[]会做两件事:
当string的析构函数被调用时,它会释放自己 _str指向的堆内存(0x100)。这时候:
四、后续操作触发崩溃 当继续使用这个vector< string >时,比如:
这时候新空间的s_new析构函数会再次尝试释放_str指向的 0x100 内存 —— 但这块内存早就被旧空间的s_old释放过了!这就是 “双重释放”(同一内存被释放两次),是 C++ 中最常见的内存错误之一,会直接导致程序崩溃。

当模板参数 T 是整型(如 int、double 等 POD 类型)时,不会出现上述 bug,核心原因在于POD 类型的内存模型和资源管理特性,具体分析如下:
以 int 为例,它的内存模型是 “值语义”—— 变量本身直接存储数值,没有指向堆内存的指针或动态资源。
由于 int 没有动态分配的资源(如堆内存、文件句柄等),浅拷贝不会导致 “资源共享” 或 “重复释放”—— 旧空间的 int 被释放时,只是简单的内存回收,新空间的 int 数值不受影响,后续使用完全正常。
总结 整型等POD 类型无动态资源管理逻辑,memcpy 的浅拷贝能直接复制 “数值本身”,不会引发资源冲突;而 std::string 是带动态资源的非 POD 类型,浅拷贝会破坏其资源的独立管理,从而导致崩溃。
总的来说扩容的时候把旧空间的数据拷贝到新空间这个过程要多一步考虑,只是单一的进行深拷贝也不好,深拷贝一般都需要更多的性能开销

库里面是使用的一种比较复杂的类型萃取技术,如果是内置类型直接memcpy


我这里写两种简单的解决方案:
方案 1:tmp[i] = _start[i](赋值深拷贝) 当T是std::string这类带动态资源的类型时,tmp[i] = _start[i]会调用string的赋值运算符。std::string的赋值运算符是深拷贝逻辑—— 它会为tmp[i]重新分配一块独立的堆内存,将_start[i]的字符数据完整复制过去,确保tmp[i]和_start[i]的资源完全独立。

方案 2:std::swap(tmp[i], _start[i])(资源所有权交换) std::swap针对std::string会调用其成员 swap 函数,交换两个string的内部资源(如字符缓冲区指针、大小、容量)。

补充:这里如果是内置类型会调用算法库的swap函数模板的交换
核心逻辑:逐个元素的 “语义级拷贝” 通过 for 循环逐个处理每个元素,利用 C++ 类的赋值运算符或 swap 函数的 “深拷贝 / 资源转移” 特性,替代了memcpy的 “字节级浅拷贝”。
总结 这种方案的本质是利用 C++ 类的 “面向对象语义”(赋值、swap 的深拷贝 / 资源管理),替代底层的 “字节级浅拷贝”,从而保证无论是 POD 还是非 POD 类型,都能安全完成扩容时的数据迁移,避免内存崩溃。
vector.h
#pragma once
#include<iostream>
#include<assert.h>
#include<vector>
using namespace std;
namespace yunze
{
template<class T>
class vector
{
public:
//typedef T* iterator;//定义迭代器
using iterator = T*;//和typedef作用一样
using const_iterator = const T*;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
//新增const版本的begin/end
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
//看似什么都没写,实际上会用缺省值走初始化列表
vector()
{
}
vector(initializer_list<T> i1)
{
//这里直接使用范围for遍历
//否则使用...的迭代器太麻烦
reserve(i1.size());
for (const auto& e : i1)
{
push_back(e);
}
}
~vector()
{
if (_start)
{
delete[] _start;
}
_start = _finish = _end_of_storage = nullptr;
}
////传统写法
////v2(v1)
////v就是v1,this就是v2
//vector(const vector<T>& v)
//{
// //成员函数前没有显示写就是v2在调用
// reserve(v.size());
// for (const auto& e : v)
// {
// push_back(e);
// }
//}
////v1 = v3,传统写法
////传值返回还要调用拷贝,使用引用返回
////返回自己以支持连续赋值
//vector<T>& operator=(const vector<T>& v)
//{
// //排除自己给自己赋值
// if (this != &v)
// {
// //赋值前先把左边的对象的数据清理掉(不释放空间)
// clear();
// //容量够就不用扩容
// reserve(v.capacity());
// for (const auto& e : v)
// {
// push_back(e);
// }
// }
// return *this;
//}
vector(size_t n, const T& val = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
vector(int n, const T& val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
//函数模板,迭代器不一定是vector迭代器,也可以是其他容器的迭代器
template<class InputInterator>
vector(InputInterator first, InputInterator last)
{ //提前一次性开好空间
//迭代器区间左闭右开,减了就是个数
//reverse(last - first);
while (first != last)
{
push_back(*first);
++first;
}
}
//拷贝构造现代写法
vector(const vector<T>& v)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
}
//指针是内置类型,内置类型的交换可以直接std::swap通用模板函数来完成
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
//v1 = v3
//赋值现代写法
vector<T> operator=(vector<T> tmp)
{
swap(tmp);
return *this;
}
void clear()
{
_finish = _start;
}
bool empty() const
{
return _start == _finish;
}
void reserve(size_t n)
{
//n比capacity小,不进行缩容
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
//把旧空间的数据拷贝过来
//第一次进来没有旧空间,(特殊处理)
if (_start)
{
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];//如果是string,调用string的赋值深拷贝
std::swap(tmp[i], _start[i]);//如果是string,调用string的交换,交换资源指向
}
//memcpy(tmp, _start, sizeof(T) * sz);
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
//不能+capacity(),此时的_end_of_storage还未更新
_end_of_storage = _start + n;
}
}
size_t size() const
{
return _finish - _start;
}
//这里的[]既可以读也可以写
T& operator[](size_t i)
{
assert(i < size());
return _start[i];
}
//这里的[]只能读
const T& operator[](size_t i) const
{
assert(i < size());
return _start[i];
}
//类比库中resize的写法
void resize(size_t n, T val = T())
{
if (n < size())
{
_finish = _start + n;
}
else {
//直接扩容,是否扩容reserve内会检查
reserve(n);
while (_finish < _start + n)
{
*_finish = val;
++_finish;
}
}
}
size_t capacity() const
{
return _end_of_storage - _start;
}
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
//满了
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
//这里我自己写的vector开空间不用内存池,用new
//new对于自定义类型会调用对应默认构造
//不用担心自定义类型对象赋值给随机值的问题
*_finish = x;
++_finish;
}
void pop_back()
{
assert(!empty());
--_finish;
}
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
//空间满了,扩容
if (_finish == _end_of_storage)
{
//若扩容要特殊处理
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
//此时的_start是新空间的_start
pos = _start + len;
}
//在pos位置插入数据
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
return pos;
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
//返回删除数据的下一个位置
return pos;
}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
}test.cpp
#define _CRT_SECURE_NO_WARNINGS 666
#include"vector.h"
namespace yunze
{
//支持迭代器就支持范围for
void Print(const vector<int>& v)
{
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
////const调const[]版本,只可以读
//for (size_t i = 0; i < v.size(); i++)
//{
// //v[0]++;//const对象只能读不能写
// cout << v[i] << " ";
//}
//cout << endl;
}
void test_vector1()
{
yunze::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(5);
v.push_back(5);
v.push_back(5);
v.push_back(5);
v.push_back(5);
v.push_back(5);
//普通对象调普通[]版本
v[0]++;
Print(v);
}
void test_vector2()
{
yunze::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//v.push_back(5);
Print(v);
v.insert(v.begin(), 0);
Print(v);
//在下标为3的位置插入30
auto it = v.begin() + 3;
v.insert(it, 30);
Print(v);
}
void test_vector3()
{
yunze::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
Print(v);
v.erase(v.begin());
Print(v);
//删除下标为2位置的值
auto it = v.begin() + 2;
v.erase(it);
Print(v);
}
void test_vector4()
{
yunze::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
//Print(v);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
////删除所有偶数
//auto it = v.begin();
//while (it != v.end())
//{
// if (*it % 2 == 0)
// {
// v.erase(it);
// }
// ++it;
//}
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
it = v.erase(it);
}
else
{
++it;
}
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
void test_vector5()
{
yunze::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
Print(v);
v.resize(3);
Print(v);
v.resize(20, 5);
Print(v);
}
void test_vector6()
{
yunze::vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
Print(v1);
yunze::vector<int> v2(v1);
Print(v2);
//原生写法
//yunze::vector<int> v3({ 10,20,30,40 });
//隐式类型转换+优化
yunze::vector<int> v3 = { 10,20,30,40 };
v1 = v3;
Print(v1);
yunze::vector<int> v4(10u, 1);
//yunze::vector<int> v4(10, 1);
Print(v4);
//实参类型一个为int,一个为char
yunze::vector<char> v5(10, 'x');
}
//这里有隐式类型转换,string支持用const char* 构造
void test_vector7()
{
yunze::vector<string> v1;
v1.push_back("111111111111111111111111");
v1.push_back("111111111111111111111111");
v1.push_back("111111111111111111111111");
v1.push_back("111111111111111111111111");
v1.push_back("111111111111111111111111");
for (auto& e : v1)
{
cout << e << " ";
}
cout << endl;
}
}
using namespace yunze;
int main()
{
//test_vector1();
//test_vector2();
//test_vector3();
//test_vector4();
//test_vector5();
//test_vector6();
test_vector7();
//C语言部分的定义
int i = 0;
//C++98后还可以这样定义
int j = int();
int k = int(1);
//C++11后还可用花括号初始化
int y = {};
int t = { 1 };
int z{ 2 };
return 0;
}