std::unique_ptr不能拷贝,因其设计目标是独占所有权,拷贝会导致二次释放或悬空指针;编译器禁用拷贝构造和拷贝赋值,仅支持移动语义实现所有权转移。
std::unique_ptr 为什么不能拷贝?
因为它的设计目标就是“独占所有权”——同一时间只允许一个
持有并管理某块堆内存。一旦允许拷贝,就会出现两个对象同时认为自己负责
同一块内存,导致二次释放(
)或悬空指针。
所以编译器直接禁用了拷贝构造函数和拷贝赋值运算符。你写
会报错:
。
能用的是移动语义:
—— 此时
变成空(
),所有权明确移交
函数传参/返回时也必须用移动:
或
别试图用
拿裸指针再 new 一个
,这会造成重复管理
如何正确初始化和转移所有权?
最安全的初始化方式是用
,它原子地分配内存并构造对象,避免异常安全问题(比如
中 B 抛异常会导致 A 泄漏)。
转移所有权只有两种合法路径:移动构造、移动赋值。没有隐式转换,没有引用传递“共享”——哪怕你传
,也只是读取,不改变所有权。
立即学习
“
C++免费学习笔记(深入)
”;
推荐初始化:
,而不是
转移示例:
—— 执行后
函数返回可直接写
,RVO + 移动优化会让开销几乎为零
自定义删除器时要注意什么?
默认删除器是
,但如果你管理的不是普通
出来的内存(比如
、C API 返回的资源、文件句柄),就必须提供匹配的删除器。否则行为未定义——轻则泄漏,重则崩溃。
C知道
CSDN推出的一款AI技术问答工具
下载
删除器类型会成为
模板参数的一部分,影响类型兼容性。带状态删除器(比如捕获了某个上下文的 lambda)还会增加对象大小。
函数指针删除器:
lambda 删除器(无捕获)可推导类型:
别把
和自定义删除器混用;传入错误删除器是静默 UB,调试器通常不报
和 raw pointer / shared_ptr 对比时的关键取舍点
不是万能替代品。它比裸指针安全,但比
更轻量、无引用计数开销,适合明确生命周期归属的场景(如工厂函数返回、容器元素、成员变量)。
但它不解决“多个地方需要访问同一资源”的问题——这时候该用
或者干脆用引用/裸指针加文档约束。强行用
然后到处
,等于放弃 RAII,只剩语法糖。
性能敏感且所有权清晰 → 选
需要共享生命周期管理 → 用
,别自己封装
只是临时观察数据(不干预生命周期)→ 直接用
或
,更直白,没歧义
容器里存
是常见模式,但注意:移动后原位置为空,迭代时要检查
真正容易被忽略的不是语法,而是所有权边界的界定——比如函数该接收
还是
,返回该用
还是
。这些决定直接影响调用方能否写出无泄漏、无悬空的安全代码。
std::unique_ptrdeletedouble freeauto p2 = p1;use of deleted functionauto p2 = std::move(p1);p1p1 == nullptrvoid take(std::unique_ptr p) std::unique_ptr make_int() get()unique_ptrstd::make_uniquenew A(), new B()const std::unique_ptr& 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() == nullptrreturn std::make_unique(...); deletenewmallocunique_ptrstd::unique_ptr p(new int, [](int* p) { free(p); }); auto p = std::unique_ptr(static_cast(malloc(sizeof(int))), [](int* p){free(p);}); std::default_deletestd::unique_ptrshared_ptrshared_ptrunique_ptrget()unique_ptrshared_ptrT*T&unique_ptrif (p)unique_ptrconst T*unique_ptrT&