C++中实现自定义容器的移动语义

在C++11及之后的标准中,移动语义成为提升性能的核心特性之一。本文将通过实现一个简单的自定义动态数组(类似std::vector)来演示如何为自己的容器类添加移动构造函数、移动赋值运算符以及相应的资源管理逻辑。

1. 设计思路

我们定义一个名为 SimpleVector 的模板类,用于存储任意类型的数据。核心成员包括:

  • T* data_:指向动态分配的数组。
  • size_t size_:当前元素个数。
  • size_t capacity_:已分配的容量。

为了实现移动语义,需要满足以下条件:

  • 移动构造函数:将源对象的资源指针转移到新对象,源对象置为安全状态。
  • 移动赋值运算符:先释放目标对象已有资源,再将源对象的资源指针转移过去,源对象置安全状态。
  • 自定义析构函数:释放资源。

2. 代码实现

#include <cstddef>
#include <utility>
#include <stdexcept>
#include <iostream>

template<typename T>
class SimpleVector {
public:
    SimpleVector() noexcept : data_(nullptr), size_(0), capacity_(0) {}

    // 析构函数
    ~SimpleVector() { delete[] data_; }

    // 复制构造(仅示例,未实现移动复制)
    SimpleVector(const SimpleVector& other)
        : data_(other.size_ ? new T[other.capacity_] : nullptr),
          size_(other.size_), capacity_(other.capacity_) {
        std::copy(other.data_, other.data_ + other.size_, data_);
    }

    // 移动构造函数
    SimpleVector(SimpleVector&& other) noexcept
        : data_(other.data_), size_(other.size_), capacity_(other.capacity_) {
        // 置源对象为空状态
        other.data_ = nullptr;
        other.size_ = 0;
        other.capacity_ = 0;
    }

    // 移动赋值运算符
    SimpleVector& operator=(SimpleVector&& other) noexcept {
        if (this != &other) {
            delete[] data_;                 // 释放已有资源
            data_ = other.data_;
            size_ = other.size_;
            capacity_ = other.capacity_;
            // 置源对象为空
            other.data_ = nullptr;
            other.size_ = 0;
            other.capacity_ = 0;
        }
        return *this;
    }

    // 追加元素
    void push_back(const T& value) {
        if (size_ == capacity_) reserve(capacity_ == 0 ? 1 : capacity_ * 2);
        data_[size_++] = value;
    }

    // 获取大小
    size_t size() const noexcept { return size_; }

    // 索引访问
    T& operator[](size_t index) {
        if (index >= size_) throw std::out_of_range("Index out of range");
        return data_[index];
    }

    const T& operator[](size_t index) const {
        if (index >= size_) throw std::out_of_range("Index out of range");
        return data_[index];
    }

private:
    void reserve(size_t new_capacity) {
        if (new_capacity <= capacity_) return;
        T* new_data = new T[new_capacity];
        for (size_t i = 0; i < size_; ++i) new_data[i] = std::move(data_[i]);
        delete[] data_;
        data_ = new_data;
        capacity_ = new_capacity;
    }

    T* data_;
    size_t size_;
    size_t capacity_;
};

3. 使用示例

int main() {
    SimpleVector <int> v1;
    for (int i = 0; i < 5; ++i) v1.push_back(i);

    // 通过移动构造创建 v2
    SimpleVector <int> v2 = std::move(v1);

    std::cout << "v2.size() = " << v2.size() << std::endl;  // 输出 5
    std::cout << "v1.size() = " << v1.size() << std::endl;  // 输出 0,已置空

    // 继续使用 v2
    for (size_t i = 0; i < v2.size(); ++i) std::cout << v2[i] << ' ';
    std::cout << std::endl;

    // 移动赋值
    SimpleVector <int> v3;
    v3 = std::move(v2);
    std::cout << "v3.size() = " << v3.size() << std::endl;  // 输出 5
    std::cout << "v2.size() = " << v2.size() << std::endl;  // 输出 0

    return 0;
}

4. 关键点回顾

  1. 移动构造 必须使用 noexcept 以便在标准容器中安全使用。
  2. 资源转移 时要把源对象置为“空”状态(指针为 nullptr,大小为0)。
  3. 自定义析构 负责真正释放资源,防止内存泄漏。
  4. 移动赋值 前先释放自身已有资源,避免资源泄漏。

通过上述实现,SimpleVector 就具备了完整的移动语义,能够在大规模数据移动时显著提升性能,且使用方式与 std::vector 类似。

发表评论