C++如何实现简单的智能指针MySharedPtr_C++手写引用计数机制【原理】

1次阅读

mysharedptr 核心是封装原始指针与堆上共享的控制块(含t ptr和int count),拷贝/移动/析构时通过原子增减引用计数并条件释放资源,reset需处理nullptr,operator/->须重载const/non-const版本返回引用以保语义正确。

C++如何实现简单的智能指针MySharedPtr_C++手写引用计数机制【原理】

MySharedPtr 的核心结构怎么组织

手写 MySharedPtr 本质是封装原始指针 + 独立的引用计数对象。不能把计数器直接塞进模板类里,否则每个 MySharedPtr 实例都有一份计数,完全失去共享意义。

正确做法是用一个上分配的控制块(control block),里面存 int* ref_count 和原始指针 T* ptr;所有指向同一对象的 MySharedPtr 共享这个控制块。

常见错误:在构造函数里直接 new int(1) 当计数器,但忘了存 ptr —— 后续 delete 时无从得知该删谁。

  • 控制块建议定义为私有嵌套结构:Struct ControlBlock { T* ptr; int* count; }
  • 构造时统一用 new ControlBlock{ptr, new int(1)},确保 ptrcount 绑定
  • 拷贝构造/赋值时只增 *count,不重新分配控制块

拷贝、移动、析构时引用计数怎么安全更新

引用计数增减必须严格配对,且析构时要区分“自己是不是最后一个使用者”。典型崩溃场景:两个 MySharedPtr 指向同一对象,其中一个析构后把 ptr 删了,另一个再访问就 segmentation fault

立即学习C++免费学习笔记(深入)”;

关键逻辑在析构函数:先 --(*count),再判断是否为 0;为 0 才 delete ptrdelete count。顺序不能反,否则计数器先被删,-- 就操作野指针。

  • 拷贝构造:复制控制块指针,然后 ++(*count)
  • 移动构造:把对方的控制块指针拿过来,再把对方设为 nullptr,**不修改计数**
  • 析构:仅当 control_block != nullptr 时才操作计数和释放
  • 赋值运算符需自赋值检查 + 先释放旧资源再接管新资源

reset() 和 get() 这些接口怎么避免裸指针误用

get() 返回裸指针本身没有问题,但用户可能把它存下来长期使用,而 MySharedPtr 已经析构——这是悬空指针根源。手写时无法阻止这种误用,但可以加注释警告,或在 debug 模式下用 weak reference 检查(较重,一般不实现)。

reset() 是最容易出错的接口:它要释放当前资源,再绑定新指针。常见疏漏是没处理“重置为 nullptr”的情况,导致控制块泄漏。

  • reset(T* p = nullptr) 必须接受 nullptr,此时只清理旧资源,不新建控制块
  • 如果新 p 非空,要新建控制块;如果旧控制块存在,按析构逻辑减计数并可能释放
  • 不要在 reset() 里调用 delete ptr —— 这事应由控制块的析构逻辑统一做

为什么 operator-> 和 operator* 要返回引用而不是值

operator* 返回 T&operator-> 返回 T*,这是为了支持链式调用和原生指针语义一致。如果 operator* 返回 T(值),就会触发拷贝构造,既低效又可能破坏对象唯一性(比如 T 是 non-copyable 类型)。

更隐蔽的问题是 const 正确性:const MySharedPtr<t></t> 应该只能获得 const T&const T*。所以必须重载两组:T& operator*()const T& operator*() const

  • 返回 *control_block->ptr 即可,前提是 control_block 非空(可加 assert 或抛异常)
  • control_block == nullptr,访问 operator*operator-> 属于未定义行为,不必主动检查,但文档要写清
  • 别忘了 operator==operator!= 比较的是所指对象地址,不是控制块地址

复杂点在于控制块生命周期和线程安全——单线程版本只需用 int 计数,但多线程下必须用 std::atomic_int,否则 ++/-- 非原子会导致计数错误。这个细节常被忽略,一上多线程环境就崩溃。

text=ZqhQzanResources