按值传递 std::unique_ptr 会触发移动,原指针变空;应根据是否接管资源选择传值(接管)或 const 引用/裸指针(仅访问)。

直接传 std::unique_ptr 值会触发移动,原指针变空
这是最常踩的坑:把 std::unique_ptr 按值传递进函数,比如 void foo(std::unique_ptr<int> p)</int>,调用时 foo(std::move(ptr)) 确实能编译,但 ptr 会立刻变成 nullptr。如果你后续还打算用它,就会出错。
原因很简单:std::unique_ptr 的拷贝构造函数被显式删除了,按值传参只能走移动构造——所有权强制转移。
- 适用场景:函数确实要接管资源(比如工厂函数、销毁逻辑)
- 不适用场景:只是想读/修改对象内容,不打算释放内存
- 检查方式:调用后打印
if (ptr) { ... },很可能为false
想只读访问?用 const std::unique_ptr<t>&</t> 或裸指针
如果函数只是需要访问 *ptr 或调用 ptr->method(),别动所有权。传 const std::unique_ptr<t>&</t> 最安全直观;更轻量的做法是传 T* 或 T&(前提是确定指针非空)。
示例对比:
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
void inspect(const std::unique_ptr<int>& p) { std::cout << *p; } // OK,不碰所有权
void inspect_raw(int* p) { std::cout << *p; } // 更通用,但需确保非空
-
const std::unique_ptr<t>&</t>能体现“我依赖的是 unique_ptr 管理的对象”,语义清晰 -
T*性能零开销,且兼容std::shared_ptr、原始new结果等 - 避免传
std::unique_ptr<t>&</t>(非常量左值引用)——容易误写成移动或重置
函数要修改所指对象但不改变所有权?传 T& 最干净
比如一个函数要给智能指针指向的对象赋值、调用成员函数、或做其他不涉及内存管理的操作,T& 是最直接的选择。它不暴露内存管理细节,调用方也完全不用考虑移动语义。
示例:
void reset_value(int& x) { x = 42; }
// 调用:
auto ptr = std::make_unique<int>(10);
reset_value(*ptr); // ptr 仍有效,*ptr 现在是 42
- 比
const std::unique_ptr<t>&</t>更轻量,无模板实例化开销 - 要求调用方确保
ptr不为空,否则解引用未定义行为 - 不能用于可能为空的场景;若需处理空情况,改用
std::optional<t></t>(C++23)或加空检查
返回 std::unique_ptr 时,别用引用或指针试图“延长生命周期”
有人会想:“我把 unique_ptr 存在局部变量里,然后返回它的引用或 .get()”,这绝对不行。局部 std::unique_ptr 析构时会自动 delete 所指对象,返回的裸指针立刻悬空。
- 正确做法:函数直接返回
std::unique_ptr<t></t>(现代 C++ 编译器基本都 RVO/NRVO,无额外开销) - 错误写法:
std::unique_ptr<int>& bad() { auto p = std::make_unique<int>(1); return p; }</int></int>—— 返回局部变量引用,未定义行为 - 错误写法:
int* bad2() { auto p = std::make_unique<int>(1); return p.get(); }</int>——p析构,内存被释放
所有权转移必须显式、单向、不可逆;任何绕过 std::unique_ptr 生命周期管理的尝试,都会在某个时刻咬你一口。

















