必须用 std::unique_ptr 而不是裸指针的情况是:需要自动释放堆内存且明确禁止多所有者共享资源时,因其独占所有权、仅支持移动、编译期阻止拷贝,可杜绝手动 delete 遗漏、异常泄漏及双重释放。

什么时候必须用 std::unique_ptr 而不是裸指针
当你需要自动释放堆内存、且明确不允许多个所有者共享同一块资源时,std::unique_ptr 是唯一合理选择。它不是“更安全的替代品”,而是语义约束工具:所有权不可复制,只能移动。
常见误用场景包括:new 后手动 delete 忘记、异常路径漏释放、函数返回裸指针导致调用方责任模糊。这些都会直接导致内存泄漏或双重释放。
- 构造后立即绑定资源:
std::unique_ptr(不推荐,应优先用p(new int(42)); make_unique) - 正确初始化方式:
auto p = std::make_unique(42); - 空指针状态检查:
if (!p) { /* p 为空 */ }或if (p == nullptr) - 解引用前务必确认非空,否则触发未定义行为(无运行时检查)
std::unique_ptr 的移动语义和禁止拷贝规则
std::unique_ptr 显式删除了拷贝构造函数和拷贝赋值运算符,任何试图复制它的操作都会在编译期报错,错误信息类似:use of deleted function ‘std::unique_ptr<_tp _dp>::unique_ptr(const std::unique_ptr<_tp _dp>&)’。
移动是唯一合法的所有权转移方式:
立即学习“C++免费学习笔记(深入)”;
std::unique_ptra = std::make_unique (100); std::unique_ptr b = std::move(a); // a 现在为空,b 持有原资源 // std::unique_ptr c = a; // 编译失败
- 函数参数传递:用
std::unique_ptr接收移动,或直接按值传(隐式移动)&& - 函数返回:直接返回
std::unique_ptr,调用方自动获得所有权 - 容器中存储:
std::vector<:unique_ptr>> vec;合法;但不能用std::shared_ptr或裸指针混用逻辑
自定义删除器(deleter)的实际用途和陷阱
默认情况下 std::unique_ptr 用 delete 释放内存,但很多 C 风格 API 返回的资源需要特定清理函数,比如 fopen/fclose、malloc/free、Windows CreateFile/CloseHandle。
这时必须提供自定义 deleter,否则资源不会被正确释放:
auto file_deleter = [](FILE* f) { if (f) fclose(f); };
std::unique_ptr fp(fopen("test.txt", "r"), file_deleter); - deleter 类型必须作为模板参数显式写出,否则编译失败:
std::unique_ptr - lambda 表达式若含捕获(
[&x]{...}),无法作为模板参数推导,需改用函数对象或std::function(但会增加开销) - 数组版本必须用
std::unique_ptr,否则delete被用于new[]内存,UB
与 std::shared_ptr 混用时的典型崩溃点
绝对不能把同一个裸指针分别交给 std::unique_ptr 和 std::shared_ptr 管理,哪怕只创建一次。两者各自维护独立的控制块,最终都会尝试释放同一块内存,造成双重释放。
例如以下代码是危险的:
int* raw = new int(42); std::unique_ptru(raw); std::shared_ptr s(raw); // 错!s 和 u 各自 delete raw
- 所有智能指针必须从同一源头产生:要么全用
make_unique/make_shared,要么确保裸指针只被一个智能指针接管 - 若需升级为共享所有权,只能通过
std::shared_ptr构造函数接收std::unique_ptr(会移动并接管):std::shared_ptrs = std::move(u); - 跨 DLL 边界传递
std::unique_ptr时,注意分配器/删除器是否匹配(尤其 Windows 上 CRT 多版本问题)
实际项目里最常出问题的不是语法写错,而是所有权边界没想清楚——比如该由谁释放、哪一层该持有、是否可能被意外移动走。写完记得问自己一句:这个指针的生命周期,是否真的只属于当前作用域或对象?











