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

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)},确保ptr和count绑定 - 拷贝构造/赋值时只增
*count,不重新分配控制块
拷贝、移动、析构时引用计数怎么安全更新
引用计数增减必须严格配对,且析构时要区分“自己是不是最后一个使用者”。典型崩溃场景:两个 MySharedPtr 指向同一对象,其中一个析构后把 ptr 删了,另一个再访问就 segmentation fault。
立即学习“C++免费学习笔记(深入)”;
关键逻辑在析构函数:先 --(*count),再判断是否为 0;为 0 才 delete ptr 和 delete 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,否则 ++/-- 非原子会导致计数错误。这个细节常被忽略,一上多线程环境就崩溃。