实现自定义 std::unique_ptr:从模板到析构器的完整代码解析

在 C++17 之前,智能指针 std::unique_ptr 的实现已经相对成熟,但如果想深入了解其内部机制,最好的方法就是自己从零实现一个简化版。下面的示例演示了一个最小化的 unique_ptr,重点解释了模板参数、构造函数、析构函数、移动语义、以及删除器(deleter)的使用。

#include <cstddef>    // std::nullptr_t
#include <utility>    // std::move, std::swap
#include <type_traits> // std::enable_if, std::is_default_constructible

// 默认删除器
template<typename T>
struct default_delete {
    void operator()(T* ptr) const noexcept {
        delete ptr;
    }
};

// 简单的 unique_ptr 实现
template<typename T, typename Deleter = default_delete<T>>
class simple_unique_ptr {
    static_assert(std::is_default_constructible <Deleter>::value,
                  "Deleter must be default constructible");

private:
    T* ptr_;
    Deleter deleter_;

public:
    // 默认构造:空指针
    simple_unique_ptr() noexcept : ptr_(nullptr), deleter_() {}

    // 直接从裸指针构造
    explicit simple_unique_ptr(T* p) noexcept : ptr_(p), deleter_() {}

    // 析构时删除指针
    ~simple_unique_ptr() noexcept {
        reset();
    }

    // 禁止拷贝
    simple_unique_ptr(const simple_unique_ptr&) = delete;
    simple_unique_ptr& operator=(const simple_unique_ptr&) = delete;

    // 移动构造
    simple_unique_ptr(simple_unique_ptr&& other) noexcept
        : ptr_(other.ptr_), deleter_(std::move(other.deleter_)) {
        other.ptr_ = nullptr;
    }

    // 移动赋值
    simple_unique_ptr& operator=(simple_unique_ptr&& other) noexcept {
        if (this != &other) {
            reset();               // 先释放自身资源
            ptr_ = other.ptr_;     // 转移指针
            deleter_ = std::move(other.deleter_);
            other.ptr_ = nullptr;  // 让 source 成为空
        }
        return *this;
    }

    // 访问指针
    T* get() const noexcept { return ptr_; }
    T& operator*() const noexcept { return *ptr_; }
    T* operator->() const noexcept { return ptr_; }

    // 检查是否为空
    explicit operator bool() const noexcept { return ptr_ != nullptr; }

    // 重置指针,传入新指针
    void reset(T* p = nullptr) noexcept {
        if (ptr_) deleter_(ptr_);
        ptr_ = p;
    }

    // 释放所有权
    T* release() noexcept {
        T* old = ptr_;
        ptr_ = nullptr;
        return old;
    }

    // 手动交换
    void swap(simple_unique_ptr& other) noexcept {
        std::swap(ptr_, other.ptr_);
        std::swap(deleter_, other.deleter_);
    }
};

// 自定义删除器示例
struct FileCloser {
    void operator()(std::FILE* fp) const noexcept {
        if (fp) std::fclose(fp);
    }
};

int main() {
    // 使用默认删除器
    simple_unique_ptr <int> p1(new int(42));
    if (p1) {
        std::cout << *p1 << '\n';
    }

    // 移动构造
    simple_unique_ptr <int> p2(std::move(p1));
    // p1 现在为空
    if (!p1) std::cout << "p1 is empty\n";

    // 自定义删除器
    simple_unique_ptr<std::FILE, FileCloser> file(
        std::fopen("test.txt", "w"), FileCloser{}
    );
    // 文件将在退出时自动关闭
}

代码要点解读

  1. 模板参数
    simple_unique_ptr<T, Deleter> 允许用户自定义删除器,从而支持数组、FILE*、以及自定义资源。默认删除器 default_delete<T> 对于普通对象使用 delete,与标准库一致。

  2. 构造与析构
    构造函数仅保存裸指针,析构函数在销毁前调用 reset(),确保资源释放。reset() 负责调用删除器并将内部指针置为 nullptr

  3. 移动语义
    unique_ptr 的核心特性是所有权的唯一移动。移动构造和移动赋值实现了指针的“转移”,并把源对象的指针置为空。这样可以避免不必要的深拷贝。

  4. 删除器
    删除器对象被存储在 deleter_ 成员中,通过 std::move 在移动赋值时传递,保持所有权唯一。自定义删除器可以在构造时直接传递。

  5. 辅助功能

    • release():让调用者获取裸指针并放弃所有权。
    • swap():交换两个 unique_ptr 的资源,常用于实现移动赋值时的异常安全。
    • operator bool():可直接用于 if 语句判断是否为空。

与标准 std::unique_ptr 的区别

  • 异常安全:示例未显式处理异常安全,但可以通过 swap 与 RAII 结合实现。
  • 数组支持:标准 unique_ptr 有专门的数组模板(unique_ptr<T[]>),本实现省略。
  • 删除器可定制化:示例已展示自定义删除器用法,保持与标准一致。
  • 构造器重载:标准 unique_ptr 支持更多构造器(如 nullptr 赋值,Deleter 参数),本实现已包含最常用。

通过以上实现,你可以在自己的项目中快速复用或进一步扩展 unique_ptr 的功能,甚至添加诸如 make_unique 工厂函数、比较运算符等高级特性。祝你编码愉快!

发表评论