通过std::unique_ptr初始化std::shared_ptr

std::shared_ptr可从std::unique_ptr初始化,通过移动语义实现所有权的单向降级:独占转共享,资源安全转移,删除器被类型擦除后存入控制块。该设计允许接口解耦与延迟共享开销,提升灵活性与性能,是标准刻意支持的机制。

一、标准规定了什么

C++ 标准为 std::shared_ptr提供了一个构造函数:

复制代码
template<class Y, class D>
shared_ptr(std::unique_ptr<Y, D>&& r);

关键点:

  • 参数类型是 右值引用 ​ unique_ptr&&

  • 调用后,r被置空,不再拥有资源

  • 资源的所有权从 unique_ptr转移给 shared_ptr

这意味着:只有 unique_ptr可以被"移动"给 shared_ptr,拷贝是不允许的。


二、为什么允许:所有权的单向降级

所有权模型决定了这种转换的合法性:

模型 语义
unique_ptr 我是唯一所有者,我销毁资源
shared_ptr 我们是共同所有者,最后一个销毁资源

从独占到共享,是所有权的降级:

原来一个人说了算,现在交给一个群体共同管理。

这在逻辑上完全自洽------资源依然存在,只是释放时机从"当前作用域结束"变成了"最后一个 shared_ptr析构"。

反过来则不行:从 shared_ptr到 unique_ptr意味着强行剥夺其他共享者的权利,会导致悬垂指针和重复释放,因此标准禁止。


三、底层发生了什么

复制代码
std::unique_ptr<Foo> u = std::make_unique<Foo>();
std::shared_ptr<Foo> s = std::move(u);

执行步骤:

  1. 接管指针 :s获得 u内部管理的原始指针

  2. 复制删除器 :u的删除器被复制到 s的控制块中

  3. 置空源对象 :u内部指针变为 nullptr,析构时不再做任何事

  4. 初始化控制块 :s的强引用计数设为 1

结果:u变为空壳,s成为资源的唯一管理者(尽管它是共享指针,但当前引用计数为 1)。


四、删除器如何被保留

unique_ptr<T, D>的删除器是类型的一部分,编译期确定。

shared_ptr<T>的删除器是类型擦除的,存储在控制块中。

转换时,shared_ptr把 unique_ptr的删除器包装成一个类型无关的可调用对象,存入控制块。因此,即使 shared_ptr的模板参数只有 T,它也能在引用计数归零时调用正确的删除逻辑。

前提只有一个:删除器必须可复制。


五、为什么标准要提供这个能力

1. 接口解耦

工厂函数可以统一返回 unique_ptr,调用方按需决定是否共享:

复制代码
std::unique_ptr<Foo> create();  // 库只负责创建

// 调用方 A:不需要共享,零开销
auto u = create();

// 调用方 B:需要共享
auto s = std::shared_ptr<Foo>(create());

如果只能返回 shared_ptr,所有调用方都要承担原子引用计数的开销,即使他们不需要共享。

2. 延迟共享开销

局部作用域内用 unique_ptr管理资源(零开销),直到真正需要跨作用域共享时,才转为 shared_ptr:

复制代码
void f() {
    auto u = std::make_unique<Foo>();  // 零开销
    u->init();
    if (need_share) {
        std::shared_ptr<Foo> s = std::move(u);  // 此时才引入共享开销
        pass_to_other_thread(s);
    }
}

六、一句话总结

shared_ptr可以从 unique_ptr初始化,是因为独占所有权可以通过移动语义安全地降级为共享所有权,且删除器被完整保留在 shared_ptr的控制块中。这是标准刻意设计的能力,用于解耦资源创建与生命周期管理策略。

不需要 std::move也能编译,是因为临时对象本身就是右值,自动匹配移动构造函数。

相关推荐
liangshanbo12151 小时前
前端面试题:AI 对话中超长消息导致内存溢出,怎么解决?
java·开发语言·前端
北冥you鱼2 小时前
Go 语言 Channel 机制详解:从原理到实战
开发语言·后端·golang
谢亮_vipxieliang2 小时前
Go 并发控制进阶:sync 包高级原语与 Context 生命周期管理
开发语言·后端·golang
xuxigifxfh2 小时前
HJ5 进制转换
java·开发语言·华为机考
denlaku2 小时前
JDK 12 新特性详解
java·开发语言
Nebula_g2 小时前
JavaSE拓展:可变参数
java·开发语言·算法·安全·javase·可变参数
anew___3 小时前
《从零手写操作系统 (30):环境变量与进程上下文——export/unset与继承语义》
java·开发语言·网络·jvm·算法
Cx330❀3 小时前
Qt 多线程深度解析:从底层原理到 UI 线程与同步实战
开发语言·qt·ui·搜索引擎·性能优化·图形渲染
西西弗Sisyphus3 小时前
Qt6 实现 会动的界面像物理世界一样
c++·qt