### 如何在 C++ 中实现自定义智能指针(UniquePtr)

在现代 C++ 开发中,资源管理是程序员必须认真对待的一个重要问题。标准库提供了 std::unique_ptrstd::shared_ptr 等智能指针来简化内存管理,但在某些场景下,标准实现并不能完全满足业务需求。比如你需要一个拥有额外元数据或自定义删除器的智能指针,或者想在不使用标准库的项目中实现类似功能。本文将带你从头实现一个基本的 UniquePtr,并演示如何扩展它以满足更复杂的需求。


1. 设计目标

  • 单一所有权:与 std::unique_ptr 一致,指针对象在任意时刻只能被一个 UniquePtr 拥有。
  • RAII:对象销毁时自动释放资源,避免内存泄漏。
  • 移动语义:支持移动构造和移动赋值,转移所有权。
  • 防止拷贝:禁止拷贝构造和拷贝赋值。
  • 可定制删除器:默认使用 delete,但可以通过模板参数传入自定义删除器。
  • 扩展性:后续可添加如引用计数、线程安全等功能。

2. 基础实现

#include <utility>   // std::swap
#include <cstddef>   // std::nullptr_t

// 默认删除器,使用 delete 释放单个对象
struct DefaultDeleter {
    template <typename T>
    void operator()(T* ptr) const {
        delete ptr;
    }
};

template <typename T, typename Deleter = DefaultDeleter>
class UniquePtr {
public:
    // 类型别名,便于用户访问
    using pointer = T*;
    using element_type = T;

    /*------------------------ 构造与析构 ------------------------*/
    explicit UniquePtr(pointer ptr = nullptr, Deleter d = Deleter())
        : ptr_(ptr), deleter_(std::move(d)) {}

    ~UniquePtr() {
        reset();
    }

    /*------------------------ 禁止拷贝 ------------------------*/
    UniquePtr(const UniquePtr&) = delete;
    UniquePtr& operator=(const UniquePtr&) = delete;

    /*------------------------ 移动语义 ------------------------*/
    UniquePtr(UniquePtr&& other) noexcept
        : ptr_(other.ptr_), deleter_(std::move(other.deleter_)) {
        other.ptr_ = nullptr;
    }

    UniquePtr& operator=(UniquePtr&& other) noexcept {
        if (this != &other) {
            reset();
            ptr_ = other.ptr_;
            deleter_ = std::move(other.deleter_);
            other.ptr_ = nullptr;
        }
        return *this;
    }

    /*------------------------ 成员访问 ------------------------*/
    pointer get() const noexcept { return ptr_; }
    element_type& operator*() const noexcept { return *ptr_; }
    pointer operator->() const noexcept { return ptr_; }

    explicit operator bool() const noexcept { return ptr_ != nullptr; }

    /*------------------------ 资源释放与重置 ------------------------*/
    void reset(pointer ptr = nullptr) noexcept {
        if (ptr_ != ptr) {
            if (ptr_) deleter_(ptr_);
            ptr_ = ptr;
        }
    }

    /*------------------------ 交换 ------------------------*/
    void swap(UniquePtr& other) noexcept {
        std::swap(ptr_, other.ptr_);
        std::swap(deleter_, other.deleter_);
    }

private:
    pointer ptr_;
    Deleter deleter_;
};

/*------------------------ 友元 swap ------------------------*/
template <typename T, typename D>
void swap(UniquePtr<T, D>& a, UniquePtr<T, D>& b) noexcept {
    a.swap(b);
}

说明

  • reset() 在析构时被调用,确保资源被释放。若传入新指针,旧指针先被销毁。
  • operator*operator-> 通过 ptr_ 访问对象成员。
  • 移动构造/赋值后,源对象的指针被置为 nullptr,防止二次删除。

3. 自定义删除器示例

struct FileDeleter {
    void operator()(FILE* f) const {
        if (f) fclose(f);
    }
};

UniquePtr<FILE, FileDeleter> filePtr(fopen("log.txt", "w"));

此时,当 filePtr 超出作用域时会自动调用 fclose 关闭文件句柄。


4. 与标准库的互操作

#include <memory>   // std::unique_ptr

UniquePtr <int> myPtr(new int(10));

// 与 std::unique_ptr 的转换
std::unique_ptr <int> stdPtr = std::make_unique<int>(20);

// 通过标准库的 std::move 转移所有权
UniquePtr <int> anotherPtr = std::move(myPtr);

如果需要把 UniquePtr 转成 std::unique_ptr,可以提供一个显式转换函数:

template <typename T, typename D>
std::unique_ptr<T, D> toStdUniquePtr(UniquePtr<T, D>&& up) {
    return std::unique_ptr<T, D>(up.release(), up.getDeleter());
}

(注意 release()getDeleter() 需要在类中实现,本文未展示,但可根据需求添加。)


5. 进一步扩展

  • 线程安全:在移动/重置时使用互斥锁。
  • 引用计数:改造为 SharedPtr,实现 std::shared_ptr 的功能。
  • 自定义分配器:在构造时接受分配器,用于自定义内存池。
  • 多维数组:为数组类型 T[] 提供专门的删除器。

6. 小结

本文提供了一个完整的 UniquePtr 实现,从基本功能到自定义删除器的使用,再到与标准库的互操作。通过这种方式,你可以在不依赖 `

` 的环境下,拥有与 `std::unique_ptr` 相同的资源管理能力,并根据业务需求灵活扩展。希望对你在 C++ 项目中的内存安全设计有所帮助。

发表评论