本文将带你一步步实现一个简化版的
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 的内存分配策略。