unique_ptr所有权转移必须用std::move,因其拷贝构造和赋值被删除;std::move仅将左值转为右值引用以触发移动语义,移交后源变空,目标获得资源。

unique_ptr 的所有权转移不是“复制”,而是“移交”——不显式调用 std::move,编译器直接拒绝。
为什么必须用 std::move 才能转移所有权
因为 unique_ptr 的拷贝构造函数和拷贝赋值运算符被显式删除(= delete)。你写 auto p2 = p1; 时,编译器看到的是对 const 左值的拷贝尝试,报错信息通常是:use of deleted function 'std::unique_ptr<_tp _dp>::unique_ptr(const std::unique_ptr<_tp _dp>&)'</_tp></_tp>。
std::move 并不真正移动任何东西,它只是把左值强制转成右值引用,从而触发移动构造或移动赋值——这才是唯一合法的所有权交接通道。
- 移动后,原
unique_ptr变为空(p1 == nullptr),不能再解引用或调用get()获取有效指针 - 被移动对象进入“有效但未定义状态”:可安全析构、可再次赋值,但不可读取内容
- 如果移动的是带自定义删除器的
unique_ptr,该删除器类型必须满足MoveConstructible;捕获局部变量的 lambda 不满足,会导致编译失败
std::move 在函数传参和返回中的实际写法
函数接收所有权时,参数必须是值类型(非引用);函数交出所有权时,返回值直接是 unique_ptr 类型。中间环节全靠 std::move 显式桥接。
立即学习“C++免费学习笔记(深入)”;
常见错误是传引用还想转移所有权:void f(const std::unique_ptr<int>& p)</int> 或 void f(std::unique_ptr<int>& p)</int> 都无法让 p 离开调用方作用域——它只是被观察,没被拿走。
- 正确传参:声明为
void take(std::unique_ptr<int> p)</int>,调用时写take(std::move(p)) - 正确返回:工厂函数直接
return std::make_unique<t>(...)</t>,编译器自动应用移动语义(RVO 或移动构造) - 容器插入:向
std::vector<:unique_ptr>></:unique_ptr>添加时,用v.push_back(std::make_unique<int>(42))</int>,别用push_back(p)(会触发拷贝错误)
数组和自定义资源场景下的所有权陷阱
普通 unique_ptr<t></t> 默认用 delete,而数组必须用 delete[]。混用会触发未定义行为(UB),典型表现是程序崩溃或内存损坏。
- 数组推荐写法:
auto arr = std::make_unique<int>(100);</int>——make_unique会自动选择default_delete<t></t> - 手写构造数组必须显式指定删除器:
std::unique_ptr<int std::default_delete>> p(new int[10]);</int> - FILE* 等资源:删除器类型要完整匹配,例如
std::unique_ptr<file decltype> fp(fopen("x.txt", "r"), &fclose);</file>,漏掉decltype(&fclose)就编译不过 - 不要用
get()拿裸指针再塞进另一个unique_ptr,那等于绕过所有权系统,大概率 double-free
reset() 和 release() 的本质区别
reset() 是受控释放:它调用当前删除器销毁旧资源,并可选接管新资源;release() 是彻底放手:它只归还裸指针,之后完全由你负责生命周期。
-
p.reset();→p变空,资源已被删 -
p.reset(new int(99));→ 先删旧的,再接管新的(异常安全) -
int* raw = p.release();→p变空,但raw不受任何智能指针保护,必须手动delete raw; - 一旦用了
release(),就退出了 RAII 范畴;后续若忘记delete,就是内存泄漏;若重复delete,就是崩溃
最易被忽略的一点:所有涉及 release() 的代码,都必须配对出现明确的资源回收逻辑——它不像 reset() 那样自带兜底保障。


















