跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

C++中的std::unique_ptr是什么?(如何实现独占所有权)

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 p)
或
std::unique_ptr make_int()
别试图用
get()
拿裸指针再 new 一个
unique_ptr
,这会造成重复管理 如何正确初始化和转移所有权? 最安全的初始化方式是用
std::make_unique
,它原子地分配内存并构造对象,避免异常安全问题(比如
new A(), new B()
中 B 抛异常会导致 A 泄漏)。 转移所有权只有两种合法路径:移动构造、移动赋值。没有隐式转换,没有引用传递“共享”——哪怕你传
const std::unique_ptr&
,也只是读取,不改变所有权。 立即学习 “ C++免费学习笔记(深入) ”; 推荐初始化:
auto p = std::make_unique(42);
,而不是
std::unique_ptr(new int(42))
转移示例:
std::unique_ptr<:string> p1 = std::make_unique<:string>("hello"); auto p2 = std::move(p1);
—— 执行后
p1.get() == nullptr
函数返回可直接写
return std::make_unique(...);
,RVO + 移动优化会让开销几乎为零 自定义删除器时要注意什么? 默认删除器是
delete
,但如果你管理的不是普通
new
出来的内存(比如
malloc
、C API 返回的资源、文件句柄),就必须提供匹配的删除器。否则行为未定义——轻则泄漏,重则崩溃。 C知道 CSDN推出的一款AI技术问答工具 下载 删除器类型会成为
unique_ptr
模板参数的一部分,影响类型兼容性。带状态删除器(比如捕获了某个上下文的 lambda)还会增加对象大小。 函数指针删除器:
std::unique_ptr p(new int, [](int* p) { free(p); });
lambda 删除器(无捕获)可推导类型:
auto p = std::unique_ptr(static_cast(malloc(sizeof(int))), [](int* p){free(p);});
别把
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&
。这些决定直接影响调用方能否写出无泄漏、无悬空的安全代码。

相关文章