应优先用 std::unique_ptr 管理独占资源,因其零开销、无引用计数;仅当多所有者共享且生命周期不确定时才用 std::shared_ptr,并配合 std::weak_ptr 防循环引用。

什么时候该用 std::unique_ptr 而不是 std::shared_ptr
所有权明确、生命周期单一的场景必须优先选 std::unique_ptr。比如函数内部分配资源后返回,或作为类成员管理独占资源——它零开销、无引用计数、不允许多个指针同时指向同一对象。std::shared_ptr 适合多个所有者共享同一资源的场景(如观察者模式、缓存池),但会带来原子引用计数开销和潜在循环引用风险。
常见误用:把 std::shared_ptr 当成“更安全的 std::unique_ptr”随意使用,结果拖慢性能还掩盖了设计问题。
- 函数返回堆对象?用
std::unique_ptr移动语义传递,避免拷贝和共享语义污染 - 需要跨线程共享且无法预估生命周期?才考虑
std::shared_ptr,并搭配std::weak_ptr打破循环 - 类成员持有外部传入的资源?除非明确约定共享,否则应拒绝
std::shared_ptr参数,改用引用或std::unique_ptr转移
std::unique_ptr 的构造与移动陷阱
std::unique_ptr 禁止拷贝,只支持移动。直接赋值或传值时若忘记写 std::move(),编译器会报错:use of deleted function ‘std::unique_ptr<t>::unique_ptr(const std::unique_ptr<t>&)’</t></t>。
常见错误写法:
立即学习“C++免费学习笔记(深入)”;
std::unique_ptr<int> p1 = std::make_unique<int>(42); std::unique_ptr<int> p2 = p1; // ❌ 编译失败
正确做法:
std::unique_ptr<int> p1 = std::make_unique<int>(42); std::unique_ptr<int> p2 = std::move(p1); // ✅ p1 变为空,p2 持有资源
- 函数参数接收独占资源?声明为
std::unique_ptr<t></t>(按值)或std::unique_ptr<t>&&</t>(右值引用),避免意外保留原指针 - 数组类型需显式指定删除器:
std::unique_ptr<int> arr(new int[10])</int>,否则delete被调用而非delete[] - 自定义删除器(如关闭文件句柄)必须与类型绑定,且不能是临时 lambda(捕获变量会导致移动失败)
std::shared_ptr 的循环引用怎么破
两个 std::shared_ptr 相互持有对方所管理的对象,引用计数永远不为 0,导致内存泄漏。典型场景:双向链表节点、父-子对象关系中双方都用 std::shared_ptr。
错误示例:
struct Parent { std::shared_ptr<Child> child; };
struct Child { std::shared_ptr<Parent> parent; }; // ⚠️ 循环引用
修复方式:一方改用 std::weak_ptr,访问前调用 lock() 判断是否仍有效:
struct Child { std::weak_ptr<Parent> parent; };
// 使用时:
if (auto p = child.parent.lock()) {
// p 是有效的 std::shared_ptr<Parent>
}
-
std::weak_ptr::lock()返回std::shared_ptr,若原对象已析构则返回空指针,必须判空 - 不要用
std::weak_ptr构造std::shared_ptr后长期持有——这等于又加了一次引用,可能延长对象寿命 - 调试时可用
use_count()和weak_use_count()辅助定位循环点,但注意多线程下数值仅作参考
别忽略自定义删除器和别名构造的细节
std::unique_ptr 和 std::shared_ptr 都支持自定义删除器,但行为不同:std::unique_ptr 删除器类型是模板参数一部分,影响 sizeof;std::shared_ptr 删除器存储在控制块中,类型擦除,不影响大小但有虚函数调用开销。
别名构造(aliasing constructor)常被忽略,但它能解决“共享对象但只管理某字段”的需求,比如从 std::shared_ptr<bigstruct></bigstruct> 中提取一个字段指针,又不希望该字段生命周期短于整个结构体:
auto big = std::make_shared<BigStruct>(); std::shared_ptr<int> field(big, &big->value); // ✅ 共享 big 的生命周期,但指针指向 value
- 别名构造第二个参数是原始指针,不是偏移量,也不能是计算表达式(如
&(v[0])必须确保 v 存活) - 自定义删除器若抛异常,
std::shared_ptr保证不会传播(被noexcept抑制),而std::unique_ptr默认会终止程序,需手动设为noexcept - 用
std::make_shared创建对象时,删除器只能通过std::shared_ptr构造函数传入,不能用于make_shared本身
std::unique_ptr 的移动语义、std::shared_ptr 的控制块布局、别名构造的生存期绑定,这些细节一旦写错,轻则崩溃,重则静默泄漏。真正难的不是语法,而是判断“谁该拥有这块内存”——这个设计决策,编译器可不帮你做。











