std::unique_ptr不能拷贝,因其设计目标是独占所有权,拷贝会导致二次释放或悬空指针;编译器禁用拷贝构造和拷贝赋值,仅支持移动语义实现所有权转移。

std::unique_ptr 为什么不能拷贝?
因为它的设计目标就是“独占所有权”——同一时间只允许一个 std::unique_ptr 持有并管理某块堆内存。一旦允许拷贝,就会出现两个对象同时认为自己负责 delete 同一块内存,导致二次释放(double free)或悬空指针。
所以编译器直接禁用了拷贝构造函数和拷贝赋值运算符。你写 auto p2 = p1; 会报错:use of deleted function。
- 能用的是移动语义:
auto p2 = std::move(p1);—— 此时p1变成空(p1 == nullptr),所有权明确移交 - 函数传参/返回时也必须用移动:
void take(std::unique_ptr<int> p)</int>或std::unique_ptr<int> make_int()</int> - 别试图用
get()拿裸指针再 new 一个unique_ptr,这会造成重复管理
如何正确初始化和转移所有权?
最安全的初始化方式是用 std::make_unique,它原子地分配内存并构造对象,避免异常安全问题(比如 new A(), new B() 中 B 抛异常会导致 A 泄漏)。
转移所有权只有两种合法路径:移动构造、移动赋值。没有隐式转换,没有引用传递“共享”——哪怕你传 const std::unique_ptr<t>&</t>,也只是读取,不改变所有权。
立即学习“C++免费学习笔记(深入)”;
- 推荐初始化:
auto p = std::make_unique<int>(42);</int>,而不是std::unique_ptr<int>(new int(42))</int> - 转移示例:
std::unique_ptr<:string> p1 = std::make_unique<:string>("hello"); auto p2 = std::move(p1);</:string></:string>—— 执行后p1.get() == nullptr - 函数返回可直接写
return std::make_unique<t>(...);</t>,RVO + 移动优化会让开销几乎为零
自定义删除器时要注意什么?
默认删除器是 delete,但如果你管理的不是普通 new 出来的内存(比如 malloc、C API 返回的资源、文件句柄),就必须提供匹配的删除器。否则行为未定义——轻则泄漏,重则崩溃。
删除器类型会成为 unique_ptr 模板参数的一部分,影响类型兼容性。带状态删除器(比如捕获了某个上下文的 lambda)还会增加对象大小。
- 函数指针删除器:
std::unique_ptr<int void> p(new int, [](int* p) { free(p); });</int> - lambda 删除器(无捕获)可推导类型:
auto p = std::unique_ptr<int decltype p>(static_cast<int>(malloc(sizeof(int))), [](int* p){free(p);});</int></int> - 别把
std::default_delete和自定义删除器混用;传入错误删除器是静默 UB,调试器通常不报
和 raw pointer / shared_ptr 对比时的关键取舍点
std::unique_ptr 不是万能替代品。它比裸指针安全,但比 shared_ptr 更轻量、无引用计数开销,适合明确生命周期归属的场景(如工厂函数返回、容器元素、成员变量)。
但它不解决“多个地方需要访问同一资源”的问题——这时候该用 shared_ptr 或者干脆用引用/裸指针加文档约束。强行用 unique_ptr 然后到处 get(),等于放弃 RAII,只剩语法糖。
- 性能敏感且所有权清晰 → 选
unique_ptr - 需要共享生命周期管理 → 用
shared_ptr,别自己封装 - 只是临时观察数据(不干预生命周期)→ 直接用
T*或T&,更直白,没歧义 - 容器里存
unique_ptr是常见模式,但注意:移动后原位置为空,迭代时要检查if (p)
真正容易被忽略的不是语法,而是所有权边界的界定——比如函数该接收 unique_ptr 还是 const T*,返回该用 unique_ptr 还是 T&。这些决定直接影响调用方能否写出无泄漏、无悬空的安全代码。










