在现代 C++ 开发中,智能指针是内存管理的重要工具。标准库提供了 std::unique_ptr、std::shared_ptr 与 std::weak_ptr,但在某些特定场景下,开发者仍可能需要自定义自己的智能指针。本文将从原理出发,讲解如何实现一个简易的 MySharedPtr,并在此基础上演示如何加入线程安全、异常安全以及自定义删除器等功能。
1. 需求与设计目标
| 功能 | 说明 |
|---|---|
| 共享所有权 | 同 std::shared_ptr,允许多份指针引用同一资源。 |
| 引用计数 | 采用计数机制,资源在计数归零时释放。 |
| 线程安全 | 计数操作使用 std::atomic,确保多线程访问不出错。 |
| 自定义删除器 | 允许用户提供自定义删除逻辑(例如 delete[]、文件句柄关闭等)。 |
| 异常安全 | 在构造与销毁过程中保持异常安全,避免内存泄漏。 |
2. 关键实现细节
2.1 内部控制块(Control Block)
控制块(ControlBlock)保存两件事:
- 引用计数(`std::atomic ref_count`)
- 删除器(
std::function<void(T*)> deleter)
template<typename T>
struct ControlBlock
{
std::atomic <size_t> ref_count{1};
std::function<void(T*)> deleter;
explicit ControlBlock(std::function<void(T*)> del)
: deleter(std::move(del)) {}
};
2.2 MySharedPtr 类
template<typename T>
class MySharedPtr
{
public:
// 构造
explicit MySharedPtr(T* ptr = nullptr,
std::function<void(T*)> deleter = std::default_delete<T>())
: ptr_(ptr), ctrl_(nullptr)
{
if (ptr_) {
ctrl_ = new ControlBlock <T>(std::move(deleter));
}
}
// 复制构造
MySharedPtr(const MySharedPtr& other) noexcept
: ptr_(other.ptr_), ctrl_(other.ctrl_)
{
inc_ref();
}
// 移动构造
MySharedPtr(MySharedPtr&& other) noexcept
: ptr_(other.ptr_), ctrl_(other.ctrl_)
{
other.ptr_ = nullptr;
other.ctrl_ = nullptr;
}
// 赋值
MySharedPtr& operator=(MySharedPtr other) noexcept
{
swap(other);
return *this;
}
// 析构
~MySharedPtr()
{
dec_ref();
}
// 访问
T& operator*() const noexcept { return *ptr_; }
T* operator->() const noexcept { return ptr_; }
T* get() const noexcept { return ptr_; }
size_t use_count() const noexcept { return ctrl_ ? ctrl_->ref_count.load() : 0; }
// 重置
void reset(T* ptr = nullptr,
std::function<void(T*)> deleter = std::default_delete<T>())
{
MySharedPtr temp(ptr, std::move(deleter));
swap(temp);
}
void swap(MySharedPtr& other) noexcept
{
std::swap(ptr_, other.ptr_);
std::swap(ctrl_, other.ctrl_);
}
private:
void inc_ref() noexcept
{
if (ctrl_) ctrl_->ref_count.fetch_add(1, std::memory_order_relaxed);
}
void dec_ref()
{
if (!ctrl_) return;
if (ctrl_->ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
// 计数归零,释放资源
ctrl_->deleter(ptr_);
delete ctrl_;
}
}
T* ptr_;
ControlBlock <T>* ctrl_;
};
2.3 线程安全性
- 计数器使用 `std::atomic `,加、减操作分别采用 `fetch_add` / `fetch_sub`。
- 读取计数使用
load(),保证可见性。 - 由于所有操作都是原子性的,
MySharedPtr的复制、赋值、析构在多线程环境下都是安全的。
2.4 异常安全
- 构造函数
MySharedPtr(T*, deleter)只在new ControlBlock成功后才会持有指针;若new抛异常,资源已被ptr_拥有,但不需要显式释放,编译器会自动析构ptr_。 - 赋值操作采用“copy-and-swap”惯用法,确保在抛异常时不破坏原对象状态。
3. 使用示例
3.1 共享普通对象
int main()
{
MySharedPtr <int> p1(new int(42));
std::cout << "use_count p1: " << p1.use_count() << '\n'; // 1
MySharedPtr <int> p2 = p1; // 共享所有权
std::cout << "use_count p1: " << p1.use_count() << '\n'; // 2
p2.reset();
std::cout << "use_count p1 after reset p2: " << p1.use_count() << '\n'; // 1
}
3.2 使用自定义删除器
struct FileHandle {
FILE* fp;
};
void close_file(FileHandle* fh)
{
if (fh->fp) fclose(fh->fp);
delete fh;
}
int main()
{
FileHandle* fh = new FileHandle{fopen("log.txt", "w")};
MySharedPtr <FileHandle> sp(fh, close_file);
// ...
} // sp析构时会调用 close_file
3.3 多线程安全
void worker(MySharedPtr <int> ptr)
{
for (int i = 0; i < 1000; ++i)
std::cout << *ptr << '\n';
}
int main()
{
MySharedPtr <int> shared(new int(10));
std::thread t1(worker, shared);
std::thread t2(worker, shared);
t1.join(); t2.join(); // 所有线程共享同一对象,计数安全
}
4. 进一步扩展
| 功能 | 说明 |
|---|---|
| 弱引用(WeakPtr) | 与 std::weak_ptr 相似,提供 use_count()、expired() 与 lock()。 |
| 多继承与偏移 | 通过模板特化支持 static_cast 与 dynamic_cast 的偏移计算。 |
| 自增自减模板 | 为 MySharedPtr 提供 operator++/-- 用于计数操作(仅用于学习演示)。 |
| 内存池与自定义分配器 | 在 ControlBlock 或对象分配时使用自定义内存池,降低分配开销。 |
5. 结语
通过本文的实现,您已经了解了如何在 C++ 中从零开始构建一个功能完整且线程安全的共享智能指针。虽然 std::shared_ptr 已经足够强大,但自定义实现可以让您在满足特殊需求(如自定义删除器、非标准分配器、调试追踪等)时拥有更大的灵活性。希望这篇文章能为您在深入理解 C++ 内存管理机制的道路上提供一点帮助。