shared_ptr不是万能安全方案,需防范循环引用、裸指针误用和跨线程竞态;必须优先用make_shared创建,赋值拷贝仅增减引用计数,线程安全仅限计数操作,循环引用须用weak_ptr打破。

直接说结论:shared_ptr 不是“用了就安全”的银弹,它只管引用计数归零时释放内存;循环引用、裸指针误用、跨线程对象访问不加锁——这三类问题不处理,照样内存泄漏或崩溃。
std::make_shared 是唯一推荐的创建方式
用 std::make_shared 创建,而不是 std::shared_ptr<int>(new int(42))</int>。后者在构造控制块失败时会泄露裸指针,而 make_shared 把对象和控制块一次性分配,异常安全。
- 正确:
auto p = std::make_shared<:string>("hello");</:string> - 错误:
int* raw = new int(42); auto p = std::shared_ptr<int>(raw);</int>—— 若后续控制块分配失败,raw永远不会被 delete - 例外:需要自定义删除器(如关闭文件句柄)时,才退回到带
deleter的构造函数,且必须确保裸指针只交给一个shared_ptr初始化
shared_ptr 赋值和拷贝不复制对象,只增减引用计数
这是最常被误解的一点:看起来像“传值”,实际是共享底层资源。所有指向同一对象的 shared_ptr 实例,其 get() 返回的地址完全相同,use_count() 可查当前计数。
- 赋值
a = b;会让a原来管理的对象引用计数减一,b所指对象计数加一 -
reset()等价于赋值空指针:a.reset();→a = nullptr;,原对象计数减一 - 线程安全仅限引用计数操作本身;多个线程通过不同
shared_ptr实例读写同一对象,仍需互斥锁保护
循环引用必须用 std::weak_ptr 打破
两个 shared_ptr 相互持有(比如树节点的 parent 和 child),引用计数永远卡在 ≥1,对象无法析构 —— Valgrind 看不到泄漏,但对象生命周期失控。
- 典型场景:父子结构、观察者回调、图节点双向边
- 解法不是“少用 shared_ptr”,而是把非拥有关系的一方换成
std::weak_ptr,例如:std::weak_ptr<node> parent;</node> - 访问前必须调用
lock():if (auto p = parent.lock()) { /* p 是临时 shared_ptr,安全 */ };直接解引用weak_ptr编译不过 -
weak_ptr不参与引用计数,也不影响对象销毁时机,只提供“观察+临时升级”能力
最容易被忽略的是:weak_ptr 的 lock() 返回空,并不意味着对象刚销毁——可能它早就被其他线程释放了,只是你这次才检查到。所以业务逻辑里不能假设“lock 成功就一定长期有效”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











