unique_ptr不可拷贝,只能通过std::move()移动所有权;典型场景为函数传参(需显式move)、函数返回(自动移动)和容器插入(需move);转移后原指针为空,应检查p.get()==nullptr;带自定义删除器时类型必须严格匹配。

unique_ptr 不能拷贝,只能移动
这是最常踩的坑:直接写 unique_ptr<int> p2 = p1;</int> 会编译失败,报错类似 use of deleted function 'std::unique_ptr<_tp _dp>::unique_ptr(const std::unique_ptr<_tp _dp>&)'</_tp></_tp>。因为 unique_ptr 的拷贝构造函数和拷贝赋值运算符都被显式删除了——它天生就只允许一个所有者。
所有权转移必须通过移动语义完成,核心操作是 std::move(),它把左值“标记”为可移动状态,触发移动构造或移动赋值。
-
std::move(p1)不真正搬内存,只是把p1内部的裸指针置为nullptr,然后把原值交给接收方 - 转移后,
p1.get() == nullptr,再访问*p1会崩溃;p2拿到原资源并拥有完整控制权 - 如果源对象是右值(比如函数返回的临时
unique_ptr),连std::move都不用显式写,编译器自动移动
用 move() 转移所有权的三种典型场景
实际编码中,所有权转移主要发生在函数传参、函数返回、容器插入这三类地方,写法略有差异但本质一致。
- 函数参数接收:声明为
void take_ownership(std::unique_ptr<int> ptr)</int>,调用时写take_ownership(std::move(p1));如果参数是const std::unique_ptr<int>&</int>,那就只是观察,不转移 - 函数返回:直接
return std::make_unique<int>(42);</int>,接收方写auto p = make_int_ptr();—— 返回的临时对象自动移动,无需std::move - 放进 vector:用
vec.push_back(std::move(p1));,否则编译失败;之后p1为空,vec[0]持有资源
转移后检查空指针是必要习惯
很多人以为转移完源指针“自动失效”,就不再管它,结果在后续逻辑里误用导致段错误。C++ 不强制清空或禁止访问,只是把内部指针设为 nullptr,所以 p1 == nullptr 或 p1.get() == nullptr 是安全且推荐的判断方式。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
立即学习“C++免费学习笔记(深入)”;
- 不要依赖未定义行为,比如转移后还调用
p1.release()或p1.reset()(虽然不会崩,但语义混乱) - 调试时可在转移后立刻加
assert(!p1);,尤其在复杂条件分支里防止遗漏 - 注意:
std::move(p1)本身不改变p1的值,只是类型转换;真正清空发生在移动构造/赋值过程中
自定义删除器下转移要保持类型一致
如果 unique_ptr 带了自定义删除器(比如用 FILE* 配 fclose),移动时删除器类型必须匹配,否则编译失败。例如:
auto p1 = std::unique_ptr<FILE, decltype(&fclose)>(fopen("a.txt", "r"), &fclose);
auto p2 = std::move(p1); // OK:类型完全一致
// auto p3 = std::unique_ptr<FILE>(std::move(p1)); // ERROR:删除器类型不兼容
这意味着带删除器的 unique_ptr 更“重”,不能隐式退化为无删除器版本,转移时得确保目标变量声明类型与源一致。
真正麻烦的是跨作用域或跨模块传递带状态删除器(比如捕获 lambda),此时移动可能受限,最好一开始就用函数指针或标准删除器避免这类问题。

















