本文将带你一步步实现一个简化版的
std::vector
,从最基础的动态扩容开始,到支持移动语义、原地构造(EmplaceBack
)以及自定义内存管理(::operator new/delete
)。通过阅读本文,你将深入理解 C++ 中对象构造、析构、内存分配的底层机制。
一、Vector 数组简介
相较于 Array
(静态数组),Vector
的最大特点就是动态扩容 。
我们无需在创建时指定固定容量,可以在运行时以 O(1) 的摊销复杂度不断地向尾部插入元素,同时仍然保持随机访问的 O(1) 时间复杂度。
二、动态扩容策略
1. 连续存储与随机访问
要在 O(1) 时间内访问任意元素,必须使用连续存储空间 。这意味着 Vector
内部仍然是通过普通数组(T*
)实现的。
2. 扩容与均摊复杂度
当数组容量用尽时,我们需要重新分配更大的内存,并将原有数据拷贝过去。这一操作本身复杂度是 O(n)。
为了使多次插入操作的平均复杂度仍然保持 O(1),扩容时通常采用倍增策略 (如 2 倍扩容)。
这样,每个元素在整个生命周期内平均只会被移动常数次。
实际上,MSVC 使用 1.5 倍扩容,GCC 使用 2 倍扩容。本文采用后者。
三、基础版本实现
我们先实现一个最小可用的版本,包含以下 API:
-
PushBack(const T&)
-
operator[]
-
Size()
-
内部的
ReAlloc()
实现动态扩容
cpp
template <typename T>
class Vector {
public:
Vector() { ReAlloc(2); }
void PushBack(const T& value) {
if (m_Size >= m_Capacity)
ReAlloc(m_Size + m_Size);
m_Data[m_Size++] = value;
}
T& operator[](size_t index) { return m_Data[index]; }
const T& operator[](size_t index) const { return m_Data[index]; }
size_t Size() const { return m_Size; }
private:
void ReAlloc(size_t newCapacity) {
T* newBlock = new T[newCapacity];
if (newCapacity < m_Size)
m_Size = newCapacity;
for (size_t i = 0; i < m_Size; ++i)
newBlock[i] = m_Data[i];
delete[] m_Data;
m_Data = newBlock;
m_Capacity = newCapacity;
}
private:
T* m_Data = nullptr;
size_t m_Size = 0;
size_t m_Capacity = 0;
};
四、加入移动语义(Move 版本)
我们可以用以下类 Vector3
来观察对象复制与移动:
cpp
class Vector3 {
public:
Vector3() {}
Vector3(float scalar)
: x(scalar), y(scalar), z(scalar) {}
Vector3(float x, float y, float z)
: x(x), y(y), z(z) {}
Vector3(const Vector3& other)
: x(other.x), y(other.y), z(other.z) {
std::cout << "Copy" << std::endl;
}
Vector3(const Vector3&& other)
: x(other.x), y(other.y), z(other.z) {
std::cout << "Move" << std::endl;
}
~Vector3() {
std::cout << "Destroy" << std::endl;
}
Vector3& operator=(const Vector3& other) {
std::cout << "Copy" << std::endl;
x = other.x;
y = other.y;
z = other.z;
return *this;
}
Vector3& operator=(Vector3&& other) {
std::cout << "Move" << std::endl;
x = other.x;
y = other.y;
z = other.z;
return *this;
}
friend std::ostream& operator<<(std::ostream&, const Vector3&);
private:
float x = 0.0f, y = 0.0f, z = 0.0f;
};
std::ostream& operator<<(std::ostream& os, const Vector3& vec) {
os << vec.x << ", " << vec.y << ", " << vec.z;
return os;
}
测试代码:
cpp
int main() {
Vector<Vector3> vec;
vec.PushBack(Vector3());
vec.PushBack(Vector3(1.0f));
vec.PushBack(Vector3(1.0f, 2.0f, 3.0f));
PrintVector(vec);
return 0;
}
输出(未优化前):
cpp
Copy
Destroy
Copy
Destroy
Copy
Copy
Destroy
Destroy
Copy
Destroy
0, 0, 0
1, 1, 1
1, 2, 3
---------------------------
可以看到,每次插入都进行了多余的拷贝。
改进:支持右值引用
我们在 Vector
中新增:
cpp
void PushBack(T&& value) {
if (m_Size >= m_Capacity)
ReAlloc(m_Size + m_Size);
m_Data[m_Size++] = std::move(value);
}
同时在 ReAlloc
中也使用 std::move
:
cpp
for (size_t i = 0; i < m_Size; ++i)
newBlock[i] = std::move(m_Data[i]);
输出结果:
cpp
Move
Destroy
Move
Destroy
Move
Move
Destroy
Destroy
Move
Destroy
现在已经没有 Copy,全是 Move!
五、原地构造(EmplaceBack + Placement New)
即便如此,每次 PushBack
仍需先在外部构造一个临时对象再移动进去。
我们希望能直接在目标内存地址上构造对象 ,这就是 Placement new 的用法。
实现 EmplaceBack
cpp
template<typename... Args>
T& EmplaceBack(Args&&... args) {
if (m_Size >= m_Capacity)
ReAlloc(m_Size + m_Size);
new (&m_Data[m_Size]) T(std::forward<Args>(args)...); // 原地构造
return m_Data[m_Size++];
}
测试:
cpp
int main() {
Vector<Vector3> vec;
vec.EmplaceBack();
vec.EmplaceBack(1.0f);
vec.EmplaceBack(1.0f, 2.0f, 3.0f);
return 0;
}
输出:
cpp
Move
Move
Destroy
Destroy
效率大幅提升!
六、添加 PopBack 与析构函数
cpp
void PopBack() {
if (m_Size > 0) {
--m_Size;
m_Data[m_Size].~T();
}
}
~Vector() { delete[] m_Data; }
但此实现有风险:如果
T
的析构函数中释放资源(如delete[]
),可能导致双重析构问题。因此我们还需分离"分配内存"和"对象构造"两个步骤。
七、正确的内存管理:::operator new / delete
在 C++ 中:
-
new
做两件事:分配内存 + 调用构造函数。 -
delete
做两件事:调用析构函数 + 释放内存。
我们希望自行控制这两步,于是使用 ::operator new
和 ::operator delete
。
完整版本实现
cpp
template<typename T>
class Vector {
public:
Vector() { ReAlloc(2); }
~Vector() {
Clear();
::operator delete(m_Data, m_Capacity * sizeof(T));
}
void PushBack(T&& value) {
if (m_Size >= m_Capacity)
ReAlloc(m_Size + m_Size);
new (&m_Data[m_Size]) T(std::move(value));
++m_Size;
}
template<typename... Args>
T& EmplaceBack(Args&&... args) {
if (m_Size >= m_Capacity)
ReAlloc(m_Size + m_Size);
new (&m_Data[m_Size]) T(std::forward<Args>(args)...);
return m_Data[m_Size++];
}
void PopBack() {
if (m_Size > 0)
m_Data[--m_Size].~T();
}
void Clear() {
for (size_t i = 0; i < m_Size; ++i)
m_Data[i].~T();
m_Size = 0;
}
private:
void ReAlloc(size_t newCapacity) {
T* newBlock = (T*)::operator new(newCapacity * sizeof(T));
if (newCapacity < m_Size)
m_Size = newCapacity;
for (size_t i = 0; i < m_Size; ++i)
new (&newBlock[i]) T(std::move(m_Data[i]));
Clear();
::operator delete(m_Data, m_Capacity * sizeof(T));
m_Data = newBlock;
m_Capacity = newCapacity;
}
private:
T* m_Data = nullptr;
size_t m_Size = 0;
size_t m_Capacity = 0;
};
编译需使用 -std=c++14
或以上(因为 C++14 起支持带大小参数的 ::operator delete
)。
输出验证
cpp
Move
Move
Destroy
Destroy
1, 2, 3
---------------------------
Destroy
---------------------------
hello
程序正常退出,无内存泄漏,无双重析构!
八、总结
阶段 | 功能 | 关键技术 |
---|---|---|
基础版 | 动态扩容 | 普通 new[] |
Move版 | 高效移动 | 移动语义 std::move |
Emplace版 | 原地构造 | Placement new |
最终版 | 完整内存控制 | ::operator new/delete + 手动析构 |
至此,我们完成了一个功能齐全的简易版 std::vector
。
九、思考与拓展
-
可实现迭代器支持,兼容
for(auto& e : vec)
。 -
可增加容量控制函数
Reserve()
与Resize()
。 -
可引入异常安全机制(RAII + strong exception guarantee)。
-
深入理解
std::allocator
与 STL 的内存分配策略。