如何在 C++ 中实现自定义智能指针的移动语义

在 C++ 中,智能指针(如 std::unique_ptrstd::shared_ptr)通过 RAII 自动管理资源。若要自定义智能指针,必须正确实现移动语义,以保证资源的唯一所有权能够安全转移。下面给出一个最小可行的示例,并说明关键点。

  1. 定义类骨架

    template<typename T>
    class MyUniquePtr {
        T* ptr_;
    public:
        explicit MyUniquePtr(T* p = nullptr) noexcept : ptr_(p) {}
        ~MyUniquePtr() { delete ptr_; }
    
        // 禁止拷贝
        MyUniquePtr(const MyUniquePtr&) = delete;
        MyUniquePtr& operator=(const MyUniquePtr&) = delete;
    };
  2. 实现移动构造函数

    MyUniquePtr(MyUniquePtr&& other) noexcept
        : ptr_(other.ptr_) {   // 直接转移指针
        other.ptr_ = nullptr;   // 源对象释放时不再删除
    }
  3. 实现移动赋值运算符

    MyUniquePtr& operator=(MyUniquePtr&& other) noexcept {
        if (this != &other) {
            delete ptr_;           // 先释放当前资源
            ptr_ = other.ptr_;     // 再转移新资源
            other.ptr_ = nullptr;  // 让源对象为空
        }
        return *this;
    }
  4. 提供成员访问

    T& operator*() const { return *ptr_; }
    T* operator->() const noexcept { return ptr_; }
    T* get() const noexcept { return ptr_; }
  5. 测试

    struct Demo { int val; };
    int main() {
        MyUniquePtr <Demo> p1(new Demo{10});
        MyUniquePtr <Demo> p2 = std::move(p1);   // 移动构造
        std::cout << p2->val << std::endl;     // 输出 10
        // p1 现在为空,尝试访问会导致空指针异常
    }

关键点回顾

  • 禁止拷贝:通过 delete 拷贝构造函数和拷贝赋值运算符,确保资源所有权唯一。
  • 移动构造函数:直接转移指针并将源对象置为空,使用 noexcept 标记以满足标准库容器对移动构造函数的异常安全要求。
  • 移动赋值运算符:先释放自身已有资源,再转移指针;注意自我赋值时的保护。
  • 异常安全:在移动构造中不需要处理异常;移动赋值中若 delete 失败(不可能),则保持原始对象不变。
  • 接口:提供 operator*, operator->, get() 等常见接口,以兼容标准库习惯。

通过上述实现,你可以拥有一个既安全又符合现代 C++ 风格的自定义智能指针,既可在容器中使用,又可满足多态场景下的灵活性需求。

发表评论