C++ - 简单实现std::unique_ptr

我们手动实现一个简化版的unique_ptr,它将拥有独占所有权,并且支持移动语义,但不支持拷贝语义。
我们将实现以下功能:

  1. 构造函数:从原始指针构造

  2. 析构函数:释放资源

  3. 移动构造函数:转移所有权

  4. 移动赋值运算符:转移所有权

  5. 禁止拷贝构造和拷贝赋值

  6. 重载 operator* 和 operator->

  7. 实现 release() 和 reset() 方法

  8. 实现 get() 方法

我们不会实现自定义删除器,以保持简单。

我们要实现以下核心功能:

  • 独占所有权(禁止拷贝)

  • 移动语义

  • 资源自动释放

  • 解引用操作

MyUniquePtr.h 实现代码:

#include <iostream>
#include <utility> // for std::swap, std::move


template<typename T>
class MyUniquePtr {
private:
    T* m_ptr;  // 托管对象的原始指针

public:
    // 1. 默认构造函数
    MyUniquePtr() noexcept : m_ptr(nullptr) {}

    // 2. 从原始指针构造(显式,避免隐式转换)
    explicit MyUniquePtr(T* ptr) noexcept : m_ptr(ptr) {
        std::cout << "MyUniquePtr 构造函数" << std::endl;
    }

    // 3. 禁止拷贝构造 - 核心特性!
    MyUniquePtr(const MyUniquePtr&) = delete;

    // 4. 禁止拷贝赋值 - 核心特性!
    MyUniquePtr& operator=(const MyUniquePtr&) = delete;

    // 5. 移动构造函数 - 转移所有权
    MyUniquePtr(MyUniquePtr&& other) noexcept : m_ptr(other.release()) {
        std::cout << "MyUniquePtr 移动构造函数" << std::endl;
    }

    // 6. 移动赋值运算符 - 转移所有权
    MyUniquePtr& operator=(MyUniquePtr&& other) noexcept {
        std::cout << "MyUniquePtr 移动赋值运算符" << std::endl;
        if (this != &other) {
            reset(other.release());  // 先释放当前资源,再接管新资源
        }
        return *this;
    }

    // 7. 析构函数
    ~MyUniquePtr() {
        std::cout << "MyUniquePtr 析构函数" << std::endl;
        reset();
    }

    // 8. 解引用操作符
    T& operator*() const noexcept {
        return *m_ptr;
    }

    // 9. 箭头操作符
    T* operator->() const noexcept {
        return m_ptr;
    }

    // 10. 获取原始指针
    T* get() const noexcept {
        return m_ptr;
    }

    // 11. 释放所有权(返回指针并将内部指针置为空)
    T* release() noexcept {
        T* ptr = m_ptr;
        m_ptr = nullptr;
        return ptr;
    }

    // 12. 重置资源(删除当前对象,可选的接管新对象)
    void reset(T* ptr = nullptr) noexcept {
        // 删除当前管理的对象(如果存在)
        if (m_ptr) {
            std::cout << "删除托管对象" << std::endl;
            delete m_ptr;
        }
        m_ptr = ptr;
    }

    // 13. 交换两个unique_ptr
    void swap(MyUniquePtr& other) noexcept {
        std::swap(m_ptr, other.m_ptr);
    }

    // 14. 布尔转换(用于条件判断)
    explicit operator bool() const noexcept {
        return m_ptr != nullptr;
    }

    // 15. 与nullptr比较
    bool operator==(std::nullptr_t) const noexcept {
        return m_ptr == nullptr;
    }

    bool operator!=(std::nullptr_t) const noexcept {
        return m_ptr != nullptr;
    }
};


// 全局swap函数
template<typename T>
void swap(MyUniquePtr<T>& lhs, MyUniquePtr<T>& rhs) noexcept {
    lhs.swap(rhs);
}

 

 核心原理详解

1. 独占所有权实现

// 关键:删除拷贝构造和拷贝赋值
MyUniquePtr(const MyUniquePtr&) = delete;
MyUniquePtr& operator=(const MyUniquePtr&) = delete;

2. 移动语义实现

// 移动构造:转移所有权
MyUniquePtr(MyUniquePtr&& other) noexcept : m_ptr(other.release()) {}

// 移动赋值:先释放当前资源,再接管新资源
MyUniquePtr& operator=(MyUniquePtr&& other) noexcept {
    if (this != &other) {
        reset(other.release());
    }
    return *this;
}

3. 资源管理核心

void reset(T* ptr = nullptr) noexcept {
    if (m_ptr) {
        delete m_ptr;  // 释放当前资源
    }
    m_ptr = ptr;      // 接管新资源(可能为nullptr)
}

T* release() noexcept {
    T* ptr = m_ptr;
    m_ptr = nullptr;  // 放弃所有权但不删除
    return ptr;
}

 

测试代码

main.cpp

#include <iostream>

#include "MyUniquePtr.h"

// 测试类
class TestResource {
public:
    TestResource(const std::string& name) : m_name(name) {
        std::cout << "TestResource 构造: " << m_name << std::endl;
    }

    ~TestResource() {
        std::cout << "TestResource 析构: " << m_name << std::endl;
    }

    void print() const {
        std::cout << "TestResource::print(): " << m_name << std::endl;
    }

    std::string getName() const {
        return m_name;
    }

private:
    std::string m_name;
};



void test_basic_lifecycle() {
    std::cout << "=== 测试1: 基本生命周期 ===" << std::endl;
    MyUniquePtr<TestResource> ptr1(new TestResource("Resource1"));
    ptr1->print();
    // 离开作用域时自动销毁
}

void test_move_semantics() {
    std::cout << "\n=== 测试2: 移动语义 ===" << std::endl;

    MyUniquePtr<TestResource> ptr1(new TestResource("Resource2"));

    // 移动构造
    MyUniquePtr<TestResource> ptr2 = std::move(ptr1);
    std::cout << "移动构造后:" << std::endl;
    std::cout << "ptr1 is null: " << (ptr1 == nullptr) << std::endl;
    std::cout << "ptr2 getName: " << ptr2->getName() << std::endl;

    // 移动赋值
    MyUniquePtr<TestResource> ptr3(new TestResource("Resource3"));
    ptr3 = std::move(ptr2);
    std::cout << "移动赋值后:" << std::endl;
    std::cout << "ptr2 is null: " << (ptr2 == nullptr) << std::endl;
    std::cout << "ptr3 getName: " << ptr3->getName() << std::endl;
}

void test_release_reset() {
    std::cout << "\n=== 测试3: release和reset ===" << std::endl;

    MyUniquePtr<TestResource> ptr1(new TestResource("Resource4"));

    // release: 释放所有权但不删除
    TestResource* raw_ptr = ptr1.release();
    std::cout << "release后ptr1 is null: " << (ptr1 == nullptr) << std::endl;
    std::cout << "raw_ptr getName: " << raw_ptr->getName() << std::endl;

    // 需要手动管理原始指针
    delete raw_ptr;

    // reset: 重新设置管理的对象
    ptr1.reset(new TestResource("Resource5"));
    std::cout << "reset后ptr1 getName: " << ptr1->getName() << std::endl;
}

void test_boolean_conversion() {
    std::cout << "\n=== 测试4: 布尔转换 ===" << std::endl;

    MyUniquePtr<TestResource> ptr1(new TestResource("Resource6"));
    MyUniquePtr<TestResource> ptr2;  // 空指针

    if (ptr1) {
        std::cout << "ptr1 包含有效指针" << std::endl;
    }

    if (!ptr2) {
        std::cout << "ptr2 是空指针" << std::endl;
    }
}

// 测试拷贝禁止(编译时会报错)
void test_copy_prohibition() {
    std::cout << "\n=== 测试5: 拷贝禁止(这行代码会编译错误) ===" << std::endl;

    MyUniquePtr<TestResource> ptr1(new TestResource("Resource7"));

    // 取消下面两行的注释会导致编译错误
    // MyUniquePtr<TestResource> ptr2 = ptr1;  // 错误:拷贝构造被删除
    // MyUniquePtr<TestResource> ptr3;
    // ptr3 = ptr1;  // 错误:拷贝赋值被删除
}

int main() 
{
    test_basic_lifecycle();
    test_move_semantics();
    test_release_reset();
    test_boolean_conversion();
    test_copy_prohibition();

    return 0;
}

运行结果:

image

与标准库的差异

我们的简化版本缺少了标准库的以下特性:

  • 自定义删除器 - 标准库支持自定义删除策略

  • 数组特化 - unique_ptr<T[]> 对数组的特殊处理

  • 更完善的异常安全 - 标准库有更精细的异常处理

  • 与weak_ptr的兼容性 - 标准库中unique_ptr不能与weak_ptr配合

通过这个实现,你深入理解了:

  1. 独占所有权语义 - 为什么unique_ptr不可拷贝

  2. 移动语义的应用 - 如何通过移动来转移所有权

  3. RAII模式 - 资源获取即初始化

  4. 显式所有权转移 - release()reset()的用途

  5. 资源安全 - 自动释放避免内存泄漏

 

posted @ 2025-11-25 15:34  [BORUTO]  阅读(1)  评论(0)    收藏  举报