通过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也能编译,是因为临时对象本身就是右值,自动匹配移动构造函数。

相关推荐
xiaoye-duck12 分钟前
《C++ 工具库实战》gflags 命令行参数解析库详解
c++·第三方库
OpenAnolis小助手18 分钟前
PHP 两个严重漏洞曝光,龙蜥社区 24 小时完成 3 版本修复
开发语言·操作系统·php·龙蜥社区
狂奔的sherry1 小时前
C++ 宏生成访问函数的代价:访问权限、调试和类型检查
c++·宏·编译·预处理器·类型检查
阿尼卡~1 小时前
VS Code 配置 C 语言开发环境(Windows 10/11):从 GCC 安装到运行、断点调试,一篇搞定
c语言·开发语言·windows
划破黑暗的第一缕曙光2 小时前
[C++]:5.STL:string(2)
java·c++·redis
hhhjhl2 小时前
Undici 跨端三方库仓颉适配全流程
java·开发语言·ubuntu
染指11102 小时前
158.Agent-LangGraph-子图(共享状态和不共享状态模式)
开发语言·人工智能·python·ui·agent·agents
wangchunyu1142 小时前
JAVA JVM内存结构与运行时数据区
java·开发语言·jvm
slongzhang_3 小时前
linux 递归删除源码文件|递归删除目录下 *.js,但排除 *.min.js
linux·开发语言·javascript
云和数据.ChenGuang4 小时前
Python 常见特殊方法详解:__new__、__init__、__del__、__str__、__repr__ 与 __getattribute__
开发语言·人工智能·python·1024程序员节