C++ 的 out_ptr 和 inout_ptr

1 问题的起因

1.1 T**T&*

​ C++ 的智能指针可以通过 get() 和 * 的重载得到原始指针 T*,遇到这样的 C 风格的函数的时候:

void Process(Foo *ptr);std::unique_ptr<Foo> sp = ...;Process(sp.get()); //调用 Process 函数

Process() 函数以非抢夺的方式使用 Foo *,大家都相安无事。但是,C++ 的智能指针都是非侵入式的智能指针,如果要修改指针自身,则只能通过显式的手段,比如 reset() 成员函数。所以 C++ 的智能指针遇到这样的 C 风格的函数时,就很棘手:

bool MakeObject(Foo **pptr);
bool RefreshObject(Foo& *pptr);

没有 const 约束,大家都明白这样的接口是返回一个指针或重置一个指针。遇到这种情况,C++ 只能分步做这个事情:

std::unique_ptr<Foo> sp;
Foo *ptr = nullptr;
if(MakeObject(&ptr)) {sp.reset(ptr);
}

但是这么做就有个问题,在 MakeObject() 函数完成一个 Foo * 的初始化,到外部 sp 托管这个指针之间就有一段间隙,在这期间发生任何异常,Foo 对象和 Foo 对象分配的存储空间就随风而去,自由地飞翔。

1.2 传统解决方案

​ Raymond Chen 在资料 [2] 中给出了一种代理类的解决方法,通过代理类在智能指针和对象指针之间建立一个桥梁。就上一节的 MakeObject() 函数的使用,借助于 Raymond Chen 的思路,我们可以这样设计一个代理类:

template<typename T>
struct UniquePtrProxy {UniquePtrProxy(std::unique_ptr<T>& output): m_output(output) { }~UniquePtrProxy() { m_output.reset(m_rawPtr); }operator T** () { return &m_rawPtr; }UniquePtrProxy(const UniquePtrProxy&) = delete;UniquePtrProxy& operator=(const UniquePtrProxy&) = delete;std::unique_ptr<T>& m_output;T *m_rawPtr = nullptr;
};

UniquePtrProxy 类通过构造函数关联一个 T 类型的智能指针,通过对 T** 的重载,使得这个类可以适配需要 T** 参数的场合,给出的 T** 可被修改,并且在代理销毁的时候 reset 关联的智能指针。

std::unique_ptr<Foo> spFoo;
if (MakeObject(UniquePtrProxy<Foo>(spFoo))) {std::cout << "value = " << spFoo->GetValue() << std::endl;
}

​ 貌似天衣无缝,即使 MakeObject 内部出现了异常,只要 Foo * 的指针是有效的,利用 RAII,UniquePtrProxy 都可以正确设置智能指针,从而避免资源泄露。不过,正如 Raymond Chen 在资料 [2] 中给出的例子那样,用户如果这样写代码就很郁闷了:

if (MakeObject(UniquePtrProxy<Foo>(spFoo)) && spFoo) {std::cout << "value = " << spFoo->GetValue() << std::endl;
}

这其实是一种很合理的做法,在使用智能指针之前先检查一下指针的有效性。但是 if 表达式中的整个求值完成之前,UniquePtrProxy 创建的临时对象在 MakeObject() 函数调用完成后,会像鬼魂一样继续飘荡一段时间,当检测 spFoo 的时候,它还没有析构,spFoo 还没有被 reset,结果就是 if 代码块永远也走不到。

​ 资料 [4] 提出一种侵入式智能指针,允许直接修改内部指针,比如资料 [1] 的 retain_ptr 的实现。但是更多的库采用的是智能指针适配器的方式,通过适配器完成智能指针的侵入式操作。典型的就是 WRL 库的 ComPtrRef ,它其实是对 ComPtr 的适配器。C++ 的标准库采用的就是适配器方案。

2 C++ 23 的智能指针适配器

2.1 out_ptr_t 和 inout_ptr_t

​ C++ 23 引入了两个智能指针适配器,即 out_ptr_t 和 inout_ptr_t,分别用于应对上一节的 MakeObject() 类型 C 函数和 RefreshObject() 类型 C 函数(有时候 RefreshObject() 类型的 C 函数也是用 T** 类型参数)。对于 MakeObject() 类型 C 函数,我们可以这样使用 std::out_ptr_t 适配器:

int main() {std::unique_ptr<FooTest> spFoo;if (MakeObject(std::out_ptr_t<std::unique_ptr<FooTest>, FooTest*>(spFoo))) {std::cout << "value = " << spFoo->GetValue() << std::endl;}
}

2.2 out_ptr 和 inout_ptr

​ 正如上一节的例子,直接使用适配器需要显式指定模板参数,非常麻烦,所以 标准库还提供了两个全局函数,std::out_ptr() 和 std::inout_ptr(),这两个函数的作用就是根据函数参数推导参数类型,然后返回一个相应的适配器对象,可以简化这两个适配器的使用,比如上一节的例子,可以改成这样:

int main() {std::unique_ptr<FooTest> spFoo;if (MakeObject(std::out_ptr(spFoo))) {std::cout << "value = " << spFoo->GetValue() << std::endl;}
}

2.3 注意事项

​ C++ 标准并不建议直接使用 std::out_ptr_t 或 std::inout_ptr_t 构建一个有声明周期的临时对象,因为这样很容易导致问题,比如 2.1 节的例子,如果代码写成这样:

int main() {std::unique_ptr<FooTest> spFoo;auto&& rrr = std::out_ptr_t<std::unique_ptr<FooTest>, FooTest *>(spFoo);if (MakeObject(rrr)) {std::cout << "value = " << spFoo->GetValue() << std::endl;}
}

编译器不抱怨,但是运行异常,因为 rrr 延长了 std::out_ptr_t 临时对象的生命周期,使得它的析构在使用 spFoo 指针之后,导致 spFoo 在它消失之前一直是无效的状态。

​ 另外需要注意的是 std::inout_ptr_t 做的事情是释放智能指针原来的所有权,然后重新初始化这个智能指针。这样的操作需要独占所有权的智能指针,所以它不能用于 std::shared_ptr。还有就是 1.2 节所提到的代理或适配器的生命周期问题,std::out_ptr_t 或 std::inout_ptr_t 也存在,所以这样的代码依然是有问题的:

int main() {std::unique_ptr<FooTest> spFoo;if (MakeObject(std::out_ptr(spFoo)) && spFoo) {std::cout << "value = " << spFoo->GetValue() << std::endl;}
}

这也是使用智能指针适配器需要注意的地方。实际上,资料 [2] 中作者提出了几种解决方案,但是都不是很优雅的方案,大家感兴趣的话可以看一下这篇文章。

3 总结

​ 智能指针适配器的引入,起到了三个作用:

  • 安全封装原生指针的传递

    为许多 C 函数提供安全适配器,避免手动管理指针和资源的所有权

  • 与智能指针无缝协作

    允许 C++ 的智能指针直接用于需要 T** 和 T&* 参数的函数交互,解决智能指针需要手动释放和重置的问题,解决了此类 C 风格的 API 安全返回资源的问题

  • 统一资源管理接口

    将现有的 C 风格的资源获取和释放行为整合到 C++ 的 RAII 模型中,逐步替换为 RAII 风格,减少资源泄露的风险

总之,这些适配器工具是 C++ 进一步强化与 C 互操作性和资源管理的重要改进,通过对智能指针的自动适配,降低此类开发场景的资源泄漏风险,通过对智能指针的隐式管理,也使得代码更简洁。

参考资料

[1] retain_ptr: https://github.com/slurps-mad-rips/retain-ptr

[2] Raymond Chen: Spotting problems with destructors for C++ temporaries

[3] ComPtrRef Class: From Microsoft Windows Runtime Library

[4] P0468: A Proposal to Add an Intrusive Smart Pointer to the C++ Standard Library, 2018

[5] P1132: out_ptr - a scalable output pointer abstraction

关注作者的算法专栏
https://blog.csdn.net/orbit/category_10400723.html

关注作者的出版物《算法的乐趣(第二版)》
https://www.ituring.com.cn/book/3180

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/news/906618.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

取消 Conda 默认进入 Base 环境

在安装 Conda 后&#xff0c;每次打开终端时默认会进入 base 环境。可以通过以下方法取消这一默认设置。 方法一&#xff1a;使用命令行修改配置 在终端中输入以下命令&#xff0c;将 auto_activate_base 参数设置为 false&#xff1a; conda config --set auto_activate_ba…

数字计数--数位dp

1.不考虑前导零 2.每一位计数&#xff0c;就是有点“数页码”的意思 P2602 [ZJOI2010] 数字计数 - 洛谷 相关题目&#xff1a;记得加上前导零 数页码--数位dp-CSDN博客 https://blog.csdn.net/2301_80422662/article/details/148160086?spm1011.2124.3001.6209 #include…

Redis学习打卡-Day5-Redis 持久化

单点 Redis 的一些问题 数据丢失&#xff1a;Redis 是内存存储&#xff0c;服务重启可能会丢失数据。solution&#xff1a;实现 Redis 数据持久化。并发能力&#xff1a;单节点 Redis 并发能力虽然不错&#xff0c;但也无法满足如618这样的高并发场景。solution&#xff1a;搭…

飞书知识问答深度体验:企业AI应用落地的典范产品

飞书知识问答深度体验&#xff1a;企业AI应用落地的典范产品 产品介绍-飞书知识问答是什么与常规通用大模型相比有何优点&#xff1f;大模型横行的时代&#xff0c;飞书知识问答对普通人和企业有何影响呢&#xff1f; 场景示例-不同角色可以用飞书知识问答做什么&#xff1f;对…

Python打卡训练营学习记录Day34

知识点回归&#xff1a; CPU性能的查看&#xff1a;看架构代际、核心数、线程数 GPU性能的查看&#xff1a;看显存、看级别、看架构代际 GPU训练的方法&#xff1a;数据和模型移动到GPU device上 类的call方法&#xff1a;为什么定义前向传播时可以直接写作self.fc1(x) CPU性…

Django的请求和响应+template模板

&#x1f31f; 如果这篇文章触动了你的心弦&#xff0c;请不要吝啬你的支持&#xff01; 亲爱的读者&#xff0c; 感谢你花时间阅读这篇分享。希望这里的每一个字都能为你带来启发或是让你会心一笑。如果你觉得这篇文章有价值&#xff0c;或者它解决了你一直以来的一个疑问&a…

Python |GIF 解析与构建(2):状态机解析

Python &#xff5c;GIF 解析与构建&#xff08;2&#xff09;&#xff1a;状态机解析 目录 Python &#xff5c;GIF 解析与构建&#xff08;2&#xff09;&#xff1a;状态机解析 引言 一、状态机概述 状态机的优势与改进方向 总结 引言 在《Python &#xff5c;GIF 解…

PCB设计实践(二十六)贴片电容与插件电容的全面解析:差异、演进与应用场景

一、核心差异&#xff1a;结构与性能对比 物理结构与封装形式 贴片电容&#xff08;Surface Mount Device, SMD&#xff09;采用扁平化设计&#xff0c;外形多为长方体或圆柱体&#xff0c;直接通过焊盘固定在电路板表面。其封装材料通常为陶瓷、聚合物或铝电解层&#xff0c;外…

XC7A200T-2FFG1156I FPGA AMD Xilinx Artix-7

XC7A200T-2FFG1156I 是 AMD Xilinx Artix-7 系列的一款高性能低功耗 FPGA&#xff0c;采用 28 nm 高性能低功耗&#xff08;HPL&#xff09;工艺制造&#xff0c;核心电压在 0.95 V–1.05 V 之间&#xff0c;可在 –40 C 至 100 C 工业温度范围内稳定工作 。 逻辑资源&#xff…

LVS + Keepalived + Nginx 高可用负载均衡系统实验

1. 项目背景 在现代 Web 应用中&#xff0c;高可用性和负载均衡是至关重要的需求。本项目旨在通过 LVS&#xff08;Linux Virtual Server&#xff09;实现流量分发&#xff0c;通过 Keepalived 实现高可用性&#xff0c;通过 Nginx 提供后端服务。该架构能够确保在单点故障的情…

window 显示驱动开发-视频内存供应和回收(一)

Windows 显示驱动程序模型 (WDDM) 1.2 及更高版本用户模式显示驱动程序必须使用内存套餐和回收功能&#xff08;从Windows 8开始提供&#xff09;&#xff0c;以减少本地内存和系统内存中临时表面所需的内存开销。 最低 WDDM 版本&#xff1a;1.2 最低 Windows 版本&#xff…

什么是VR场景?VR与3D漫游到底有什么区别

在数字化时代&#xff0c;虚拟现实&#xff08;Virtual Reality, 简称VR&#xff09;场景与3D漫游作为两种前沿技术&#xff0c;改变着人们的生活方式和体验模式。通过计算机模拟真实或假想的场景&#xff0c;让用户仿佛身临其境&#xff0c;并能与虚拟环境进行互动。尽管VR场景…

JAVA查漏补缺(2)

AJAX 什么是Ajax Ajax&#xff08;Asynchronous Javascript And XML&#xff09;&#xff0c;即是异步的JavaScript和XML&#xff0c;Ajax其实就是浏览器与服务器之间的一种异步通信方式 异步的JavaScript 它可以异步地向服务器发送请求&#xff0c;在等待响应的过程中&…

客服中心大模型应用演进路线:从传统服务到超级智能助手的转型

客服中心作为企业与客户沟通的重要桥梁&#xff0c;近年来经历了从人工服务到人工智能驱动的深刻变革。本文基于"客服中心大模型应用演进路线图"&#xff0c;详细解析客服中心从传统模式向AI驱动智能服务的转型历程&#xff0c;剖析每个发展阶段的特点、应用场景及关…

使用 OpenCV 实现“随机镜面墙”——多镜片密铺的哈哈镜效果

1. 引言 “哈哈镜”是一种典型的图像变形效果&#xff0c;通过局部镜面反射产生扭曲的视觉趣味。在计算机视觉和图像处理领域&#xff0c;这类效果不仅有趣&#xff0c;还能用于艺术创作、交互装置、视觉特效等场景。 传统的“哈哈镜”往往是针对整张图像做某种镜像或扭曲变换…

Python训练营打卡——DAY33(2025.5.22)

目录 简单的神经网络 一、PyTorch的安装 二、准备工作 三、数据的准备 四、模型架构定义 五、模型训练&#xff08;CPU版本&#xff09; 1. 定义损失函数和优化器 2. 开始循环训练 3. 可视化结果 六、通俗解释 1. 环境安装&#xff08;相当于买锅碗瓢盆&#xff09;…

目标检测 Lite-DETR(2023)详细解读

文章目录 迭代高级特征跨尺度融合高效的低层次特征跨尺度融合KDA&#xff1a;Key-aware Deformable Attention 论文翻译&#xff1a; CVPR 2023 | Lite DETR&#xff1a;计算量减少60%&#xff01;高效交错多尺度编码器-CSDN博客 DINO团队的 &#xff08;Lightweight Transfo…

ES(Elasticsearch) 基本概念(一)

Elasticsearch作为当前最流行的开源搜索和分析引擎&#xff0c;广泛应用于日志分析、全文搜索、业务智能等领域。Elasticsearch是一个基于 Apache Lucene 构建的分布式搜索和分析引擎、可扩展数据存储和矢量数据库。它针对生产级工作负载的速度和相关性进行了优化。使用 Elasti…

当物联网“芯”闯入纳米世界:ESP32-S3驱动的原子力显微镜能走多远?

上次咱们把OV2640摄像头“盘”得明明白白&#xff0c;是不是感觉ESP32-S3这小东西潜力无限&#xff1f;今天&#xff0c;咱们玩个更刺激的&#xff0c;一个听起来就让人肾上腺素飙升的挑战——尝试用ESP32-S3这颗“智慧芯”&#xff0c;去捅一捅科学界的“马蜂窝”&#xff0c;…

Excel合并单元格后,如何自动批量生成序号列

1.选择整列 2.组合键&#xff1a;CtrlG 3.定位条件&#xff0c;选择“空值” 4.在第一个框中输入“MAX(”&#xff0c;鼠标选中A1框&#xff0c;后加“&#xff1a;”&#xff0c;鼠标选中前方“A1”&#xff0c;按“F4”绝对引用&#xff0c;补全右括号&#xff0c;后输入“1…