std::shared_ptr 与 std::unique_ptr删除器设计差异
本文尝试讨论了两种智能指针之间删除器设计差异的原因。
省流:
std::unique_ptr是真的持有资源,而std::shared_ptr并没有真的持有资源,持有资源的是控制块,所以删除器与std::shared_ptr没有太大意义(毕竟不是它负责删除)。
背景
在 C++ 中,std::unique_ptr 和 std::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 的设计理念:“共享控制块、多指针共同管理资源”。因此,删除器被从类型中“擦除”,成为控制块的一部分。
1346

被折叠的 条评论
为什么被折叠?



