该用 std::unique_ptr 而不是 std::shared_ptr 的核心判断标准是所有权是否需要共享:若对象生命周期明确、仅归一个作用域或类管理,就用 std::unique_ptr,因其轻量、移动语义清晰且防拷贝。

什么时候该用 std::unique_ptr 而不是 std::shared_ptr
核心判断标准是所有权是否需要共享。如果对象生命周期明确、只归一个作用域或类管理,就用 std::unique_ptr;它轻量(无引用计数开销)、移动语义清晰、且能防止意外拷贝。
常见误用场景:为图省事把所有动态对象都塞进 std::shared_ptr,结果引发循环引用或性能下降。
-
std::unique_ptr不能拷贝,只能移动 —— 这是编译器强制的保护,别试图绕过 - 工厂函数返回
std::unique_ptr是推荐做法,调用方自然获得独占所有权 - 成员变量优先用
std::unique_ptr管理大对象或可选资源(比如延迟初始化的缓存) - 若需在多个地方观察同一对象但不参与生命周期管理,用裸指针或
std::weak_ptr配合std::shared_ptr
std::shared_ptr 的构造和引用计数陷阱
引用计数不是线程安全的“读写”,而是“修改操作”(如拷贝、赋值、析构)线程安全 —— 但对象本身的访问仍需额外同步。
最常踩的坑是用原始指针重复构造 std::shared_ptr:
立即学习“C++免费学习笔记(深入)”;
int* raw = new int(42); std::shared_ptra(raw); std::shared_ptr b(raw); // ❌ 双重释放!a 和 b 各自维护一份引用计数
正确方式只有一种入口:
- 始终用
std::make_shared构造(推荐,内存分配合并,效率高)(...) - 必须从裸指针构造时,只调用一次
std::shared_ptr,之后靠拷贝传播(raw) - 避免把
this直接传给std::shared_ptr构造 —— 用std::enable_shared_from_this替代
std::unique_ptr 的自定义删除器怎么写才不崩溃
默认删除器是 delete,但遇到 C 风格资源(如 fopen/fclose、malloc/free)必须显式指定删除逻辑,否则会 UB。
关键点:删除器类型是 std::unique_ptr 模板的一部分,影响大小和 ABI 兼容性。
- 函数指针形式最简洁:
std::unique_ptrfp(fopen("x.txt", "r"), &fclose) - Lambda 若含捕获(如 [&]),就不能作为模板参数,得用
std::function—— 但会带来堆分配和虚调用开销 - 用
struct定义删除器时,确保operator()是noexcept,否则std::unique_ptr移动可能异常中止 - 数组特化必须用
std::unique_ptr,否则delete被调用而非delete[]
循环引用:为什么 std::shared_ptr 会“卡住”内存不释放
当两个对象互相持有对方的 std::shared_ptr 成员时,引用计数永远 >0,析构永远不会触发 —— 这就是循环引用。
典型场景:父子结构(如树节点)、观察者模式中的回调绑定。
- 打破循环的唯一可靠方式是把其中一端换成
std::weak_ptr -
std::weak_ptr不增加引用计数,访问前必须调用lock()转成std::shared_ptr,失败说明对象已销毁 - 不要用
std::shared_ptr管理 this,改用继承std::enable_shared_from_this,再调用shared_from_this() - 调试时可用
use_count()打印引用数,但注意它非原子,仅作诊断参考
智能指针不是万能胶水,std::unique_ptr 和 std::shared_ptr 的选择本质是建模你对资源生命周期的理解。越早明确“谁创建、谁销毁、谁只是临时使用”,就越少掉进引用计数和删除器的坑里。











