简化版unique_ptr说明其本质

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


我们平时自己写的类只有实现了移动构造函数,并且再移动构造函数内正确书写移动构造逻辑,我们的这个类才真正的能够按照移动语义做事,真正发挥移动语义的作用,同理独占指针是一个模板类是吧,它本身是不是就是进制拷贝构造和赋值运算符,并且实现了移动构造,由此形成了自身的特点,不能拷贝,只能够移动,你可以大致用代码描述下独占指针实现,伪代码也行主要说明白原理

你的理解完全正确!核心结论先明确:

  • 自定义类要发挥移动语义的价值,必须显式/隐式实现移动构造函数移动赋值运算符,并在其中完成"资源所有权转移+原对象置空"的逻辑;
  • std::unique_ptr 本质是一个模板类 ,它的核心实现逻辑就是:禁用拷贝构造/拷贝赋值实现移动构造/移动赋值,同时结合 RAII 机制自动释放资源,最终实现"独占所有权、仅可移动"的特性。

下面用简化版伪代码 还原 unique_ptr 的核心实现逻辑(省略了数组版本、自定义删除器等扩展功能,只保留核心原理),帮你直观理解:

简化版 unique_ptr 核心实现

cpp 复制代码
// 模板类:支持任意类型的动态内存管理
template <typename T>
class MyUniquePtr {
public:
    // 1. 构造函数:接管裸指针的所有权(RAII:资源获取即初始化)
    explicit MyUniquePtr(T* ptr = nullptr) : m_ptr(ptr) {}

    // 2. 禁用拷贝构造函数:保证独占性(C++11用delete)
    MyUniquePtr(const MyUniquePtr& other) = delete;

    // 3. 禁用拷贝赋值运算符:避免所有权被拷贝
    MyUniquePtr& operator=(const MyUniquePtr& other) = delete;

    // 4. 移动构造函数:转移所有权(核心!)
    // 参数是右值引用(&&),接收临时对象/被move的对象
    MyUniquePtr(MyUniquePtr&& other) noexcept {
        // 第一步:接管对方的资源(指针)
        m_ptr = other.m_ptr;
        // 第二步:将原对象的指针置空,避免析构时重复释放
        other.m_ptr = nullptr;
    }

    // 5. 移动赋值运算符:转移所有权(核心!)
    MyUniquePtr& operator=(MyUniquePtr&& other) noexcept {
        // 第一步:先释放当前对象持有的资源(避免内存泄漏)
        if (this != &other) { // 防止自赋值
            delete m_ptr;
            // 第二步:接管对方的资源
            m_ptr = other.m_ptr;
            // 第三步:原对象置空
            other.m_ptr = nullptr;
        }
        return *this;
    }

    // 6. 析构函数:自动释放资源(RAII的关键)
    ~MyUniquePtr() {
        delete m_ptr; // 独占指针析构时,释放持有的动态资源
    }

    // ========== 模拟指针的核心行为 ==========
    // 重载解引用运算符:*ptr
    T& operator*() const {
        return *m_ptr;
    }

    // 重载箭头运算符:ptr->func()
    T* operator->() const {
        return m_ptr;
    }

    // ========== 辅助方法 ==========
    // 手动释放资源(可选)
    void reset(T* new_ptr = nullptr) {
        delete m_ptr;
        m_ptr = new_ptr;
    }

    // 转移所有权(返回裸指针,原对象置空)
    T* release() {
        T* temp = m_ptr;
        m_ptr = nullptr;
        return temp;
    }

    // 获取内部裸指针(谨慎使用)
    T* get() const {
        return m_ptr;
    }

private:
    T* m_ptr; // 核心:封装的裸指针,唯一持有资源所有权
};

核心原理拆解(对应代码)

  1. 独占性的保证:禁用拷贝

    • MyUniquePtr(const MyUniquePtr&) = delete;operator=(const MyUniquePtr&) = delete; 直接禁用了拷贝构造和拷贝赋值,从语言层面杜绝了"多个 MyUniquePtr 持有同一个资源"的可能,这是"独占"的核心。
  2. 移动语义的落地:所有权转移

    • 移动构造/移动赋值的参数是右值引用(&&) ,只能接收临时对象(如函数返回值)或被 std::move 标记的对象;
    • 移动时做两件关键事:
      • 接管对方的指针(m_ptr = other.m_ptr);
      • 把原对象的指针置空(other.m_ptr = nullptr),避免原对象析构时重复释放资源。
  3. RAII 自动释放:无需手动 delete

    • 析构函数 ~MyUniquePtr() 会自动调用 delete m_ptr,只要 MyUniquePtr 对象生命周期结束(如出作用域、被销毁),就会自动释放资源,彻底解决裸指针"忘记 delete"的问题。
  4. 模拟指针行为:易用性

    • 重载 *-> 运算符,让 MyUniquePtr 能像裸指针一样使用(ptr->func()*ptr),降低使用成本。

测试代码(验证核心逻辑)

cpp 复制代码
// 测试类
class Product {
public:
    ~Product() { std::cout << "Product destroyed\n"; }
};

int main() {
    // 1. 构造:接管资源
    MyUniquePtr<Product> p1(new Product());

    // 2. 拷贝:编译失败(已禁用)
    // MyUniquePtr<Product> p2 = p1; 

    // 3. 移动:合法,转移所有权
    MyUniquePtr<Product> p2 = std::move(p1); 
    // p1 已被置空,p2 持有资源

    // 4. 函数返回:自动移动(临时对象)
    auto createProduct() {
        return MyUniquePtr<Product>(new Product());
    }
    MyUniquePtr<Product> p3 = createProduct(); // 合法,移动构造

    return 0; // p3、p2 析构,自动释放资源
}

总结

  1. unique_ptr 的核心实现逻辑:禁用拷贝 + 实现移动 + RAII 自动释放
  2. 移动语义是 unique_ptr 实现"独占且可转移"的关键:通过右值引用接管资源,同时置空原对象,保证所有权唯一;
  3. 自定义类要发挥移动语义价值,需像 unique_ptr 这样:实现移动构造/赋值,在其中完成"资源转移+原对象置空",避免浅拷贝问题。

这也是为什么说"移动语义是 unique_ptr 的灵魂"------没有移动语义,就无法在保证"独占性"的前提下实现所有权转移,只能像老旧的 auto_ptr 那样靠"劫持拷贝语义"模拟,极易出错。

相关推荐
naruto_lnq1 小时前
泛型编程与STL设计思想
开发语言·c++·算法
m0_748708052 小时前
C++中的观察者模式实战
开发语言·c++·算法
时光找茬3 小时前
【瑞萨AI挑战赛-FPB-RA6E2】+ 从零开始:FPB-RA6E2 开箱测评与 e2 studio 环境配置
c++·单片机·边缘计算
qq_537562673 小时前
跨语言调用C++接口
开发语言·c++·算法
猷咪3 小时前
C++基础
开发语言·c++
CSDN_RTKLIB3 小时前
WideCharToMultiByte与T2A
c++
星火开发设计4 小时前
类型别名 typedef:让复杂类型更简洁
开发语言·c++·学习·算法·函数·知识
蒹葭玉树4 小时前
【C++上岸】C++常见面试题目--操作系统篇(第二十八期)
linux·c++·面试
一匹电信狗4 小时前
【LeetCode_21】合并两个有序链表
c语言·开发语言·数据结构·c++·算法·leetcode·stl