std::shared_ptr 与 std::unique_ptr 删除器设计差异

本文尝试讨论了两种智能指针之间删除器设计差异的原因。

省流:
std::unique_ptr是真的持有资源,而std::shared_ptr并没有真的持有资源,持有资源的是控制块,所以删除器与std::shared_ptr没有太大意义(毕竟不是它负责删除)。

背景

在 C++ 中,std::unique_ptrstd::shared_ptr 都是现代智能指针的核心成员,它们管理资源的生命周期,并自动释放资源以防止内存泄漏。但二者在“删除器(deleter)”的处理方式上存在明显差异:

  • std::unique_ptr 的删除器是模板参数的一部分
  • std::shared_ptr 的删除器是运行时传入的参数

std::unique_ptr

删除器在 unique_ptr 中是模板参数

例子

auto deleter = [](int* p) {
    std::cout << "custom delete\n";
    delete p;
};

std::unique_ptr<int, decltype(deleter)> ptr(new int(42), deleter);

特点

删除器是 unique_ptr<T, Deleter> 类型的一部分

每种删除器都会生成一个不同的类型

优点:零运行时开销,删除器可内联,结构体大小紧凑(通常只有一个指针+删除器对象)

为什么可以这么做?

因为 unique_ptr 是独占的,只有一个拥有者,因此删除器直接绑定在实例上即可,删除工作由指针自己承担。

TinyUniquePtr

#include <iostream>

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

template <typename T, typename Deleter = DefaultDeleter<T>>
class UniquePtr {
private:
    T* ptr;
    Deleter deleter;

public:
    // 禁止拷贝
    UniquePtr(const UniquePtr&) = delete;
    UniquePtr& operator=(const UniquePtr&) = delete;

    // 构造函数
    explicit UniquePtr(T* p = nullptr, Deleter d = Deleter())
        : ptr(p), deleter(d) {}

    // 移动构造
    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;
    }

    // 析构函数
    ~UniquePtr() {
        reset();
    }

    void reset(T* p = nullptr) {
        if (ptr) {
            deleter(ptr);
        }
        ptr = p;
    }

    T* get() const { return ptr; }
    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }

    T* release() {
        T* temp = ptr;
        ptr = nullptr;
        return temp;
    }

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

std::shared_ptr

例子

auto deleter1 = [](int* p) {
    std::cout << "deleter1\n";
    delete p;
};

std::shared_ptr<int> sp(new int(42), deleter1);

特点

删除器不是类型的一部分,而是存储在“控制块”中

可以使用任意类型的删除器(如 lambda、仿函数等)

删除器支持类型擦除(如使用 std::function<void(T*)>)

多个 shared_ptr 实例共享同一个删除器

为什么这么设计?

因为 shared_ptr 是共享拥有者模型,它内部维护一个控制块(control block)来记录引用计数等元数据:

shared_ptr<T> ─────┐
                   │
                   ├─── [ Control Block ]
                   │        ├── ref count
                   │        ├── deleter
                   │        └── ptr
shared_ptr<T> ─────┘

删除器必须绑定在控制块中,这样才能在最后一个 shared_ptr 析构时使用它销毁对象。

如果删除器作为模板参数,那不同的删除器会导致 shared_ptr<T, Deleter> 类型不同,从而阻止共享控制块的可能性:

// 错误:不同删除器意味着不同类型,无法共享控制块
std::shared_ptr<int, Deleter1> p1(new int(10));
std::shared_ptr<int, Deleter2> p2(new int(10));
p2 = p1; // 不允许

TinySharedPtr

#include <iostream>
#include <functional>

template <typename T>
class SharedPtr {
private:
    // 控制块结构
    struct ControlBlock {
        T* ptr;
        size_t refCount;
        std::function<void(T*)> deleter;

        ControlBlock(T* p, std::function<void(T*)> d)
            : ptr(p), refCount(1), deleter(d) {}
    };

    ControlBlock* control;

    void release() {
        if (control) {
            if (--control->refCount == 0) {
                control->deleter(control->ptr);
                delete control;
            }
        }
    }

public:
    // 默认构造
    SharedPtr() : control(nullptr) {}

    // 构造(带删除器)
    SharedPtr(T* ptr, std::function<void(T*)> deleter)
        : control(new ControlBlock(ptr, deleter)) {}

    // 拷贝构造
    SharedPtr(const SharedPtr& other) : control(other.control) {
        if (control)
            ++control->refCount;
    }

    // 拷贝赋值
    SharedPtr& operator=(const SharedPtr& other) {
        if (this != &other) {
            release();
            control = other.control;
            if (control)
                ++control->refCount;
        }
        return *this;
    }

    // 析构
    ~SharedPtr() {
        release();
    }

    T* get() const { return control ? control->ptr : nullptr; }
    T& operator*() const { return *control->ptr; }
    T* operator->() const { return control->ptr; }

    size_t use_count() const { return control ? control->refCount : 0; }
};

为什么不能让 shared_ptr 的删除器也变成模板参数?

如果这么做:

std::shared_ptr<int, Deleter1> p1;
std::shared_ptr<int, Deleter2> p2;
p1 = p2; // 类型不兼容,无法共享控制块

这破坏了 shared_ptr 的设计理念:“共享控制块、多指针共同管理资源”。因此,删除器被从类型中“擦除”,成为控制块的一部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值