C++23的out_ptr和inout_ptr

news/2025/11/5 9:32:56/文章来源:https://www.cnblogs.com/apocelipes/p/19191735

c++23新增了一些智能指针适配器,用来扩展和简化智能指针的使用。

这次主要介绍的是std::out_ptrstd::inout_ptr。这两个适配器用法和实现都很简单,但网上的文档都比较抱歉,还缺少一些比较重要的部分,因此单开一篇文章记录一下。

out_ptr

首先从功能最简单的out_ptr讲起。

std::out_ptr其实是一个函数,返回一个类型为std::out_ptr_t的智能指针适配器,函数签名如下:

#include <memory>template< class Pointer = void, class Smart, class... Args >
auto out_ptr( Smart& s, Args&&... args );

这个函数主要是把各种智能指针包装成output parameter,以方便现有的接口使用,尤其是一些用c语言写的函数。

在继续之前我们先来复习一下output parameter是什么。这东西又叫传出参数,一次就是函数会把一部分数据写进自己的参数里返回给调用者。

通过参数返回是因为c语言和c++11之前的c++不支持多值返回也没有类似tuple这样方便的数据结构,导致函数无法直接返回两个以上的值,所以需要用一种额外的传递数据的方式。

比如我在以前的博客中提到的hsearch:int hsearch_r(ENTRY item, ACTION action, ENTRY **retval, struct hsearch_data *htab)。这个函数用来在哈希表里创建或者查找数据,查找失败的时候会返回错误码,而查找成功的时候函数返回0并把找到的数据设置给retval。这个retval就是output parameter,承载了函数除了错误码之外的返回数据。

c++里现在很少用指针类型作为output parameter了,但还有更本地化的做法——引用:int func(const char *name, Data &retval)

这类函数有几个特点:

  1. 不在乎output parameter里有什么值
  2. 函数调用期间完全享有output parameter和其资源的所有权
  3. 函数返回后output parameter通常被设置为新值

在c++提倡少用裸指针的今天,我们越来越习惯使用shared_ptr和unique_ptr,但不管哪种智能指针都很难直接适配上面这些函数,看个例子就明白了:

int get_data(const std::string &name, Data **retval)
{if (!check_name(name)) {return ErrCheckFailed;}*retval = make_data(name);return 0;
}// 使用裸指针
Data *data_ptr = nullptr;
if (auto err = get_data("name", &data_ptr); err != 0) {错误处理
} else {这里可以使用data_ptr
}

使用裸指针的时候代码比较简单,我们再来看看使用智能指针的时候:

std::unique_ptr<Data> resource;Data *data_ptr = nullptr;
if (auto err = get_data("name", &data_ptr); err != 0) {错误处理
} else {resource.reset(data_ptr);这里可以使用resource
}

代码会变得啰嗦,而且如果我们忘记了调用reset,那么资源就可能泄漏了;还有最重要的一点,我们主动使用了裸指针,而这正是我们想避免的。

这时候就需要out_ptr了。out_ptr生成的适配器会先放弃智能指针持有资源的所有权并将旧资源释放,因为如前面所说我们要调用的函数会接管资源的所有权,接着构造出的std::out_ptr_t有自动的类型转换方法,可以把智能指针转换成我们需要的T**交给函数使用,最后在函数调用结束之后再把新的资源设置回智能指针。

所以上面的例子可以改成:

std::unique_ptr<Data> resource;
if (auto err = get_data("name", std::out_ptr(resource)); err != 0) {错误处理
} else {这里可以使用resource,无需reset
}

除了代码更简洁,out_ptr还保证异常安全,即使在调用get_data的过程中抛出了异常,也不会出现资源泄漏。

利用out_ptr我们可以在使用智能指针的同时兼容老旧接口。

out_ptr和shared_ptr

如果只看函数签名,很多人会觉得out_ptr也可以直接配合std::shared_ptr使用,然而现实是多变的:

struct Data {std::string name;
};int get_data(const std::string &name, Data **retval)
{if (name == "")return 1;*retval = new Data{name};return 0;
}int main()
{std::shared_ptr<Data> resource;if (auto err = get_data("apocelipes", std::out_ptr(resource)); err != 0)std::cerr << "error\n";elsestd::cout << "success, name: " << resource->name << "\n";
}

上面的代码无法通过编译:

$ clang++ -std=c++23 test.cppIn file included from test.cpp:2:
In file included from /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/memory:948:
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__memory/out_ptr.h:38:17: error: static assertion failed due to requirement '!__is_specialization_v<std::shared_ptr<Data>, shared_ptr> || sizeof...(_Args) > 0': Using std::shared_ptr<> without a deleter in std::out_ptr is not supported.38 |   static_assert(!__is_specialization_v<_Smart, shared_ptr> || sizeof...(_Args) > 0,|                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__memory/out_ptr.h:93:10: note: in instantiation of template class 'std::out_ptr_t<std::shared_ptr<Data>, Data *>' requested here93 |   return std::out_ptr_t<_Smart, _Ptr, _Args&&...>(__s, std::forward<_Args>(__args)...);|          ^
test.cpp:19:48: note: in instantiation of function template specialization 'std::out_ptr<void, std::shared_ptr<Data>>' requested here19 |     if (auto err = get_data("apocelipes", std::out_ptr(resource)); err != 0)|                                                ^
1 error generated.

报错虽然很长但只要关注前几行就行了,错误的原因很明显,std::shared_ptr要配合out_ptr使用就必须显示提供deleter。

这是因为对于std::shared_ptr,deleter并不是类型的一部分,通常是我们通过构造函数或者reset方法穿进去的,为了能100%正确释放资源,我们需要手动把合适的deleter传进去;相对地deleter是std::unique_ptr类型的一部分,out_ptr可以直接从类型参数里得到合适的deleter从而正确释放资源。

这也是为什么out_ptr还有变长参数,这些参数就是为了std::shared_ptr或者其他有特殊要求的类似智能指针准备的。

好在上面的代码稍作修改就能正常使用:

int main()
{std::shared_ptr<Data> resource;
-   if (err = get_data("apocelipes", std::out_ptr(resource)); err != 0)
+   if (err = get_data("apocelipes", std::out_ptr(resource, std::default_delete<Data>{})); err != 0)std::cerr << "error\n";elsestd::cout << "success, name: " << resource->name << "\n";
}

std::default_delete<T>会调用delete或者delete[]来释放资源,正好我们这里可以利用它。shared_ptr平时也默认使用的这个。

修改很简单,但网上讲这点的文档不多,因此多记一笔。另外基于out_ptr会临时转移所有权这点来看,共享所有权模型的std::shared_ptr其实并不适合使用out_ptr,虽然标准没有禁止甚至还要求额外做检测(用于初始化shared_ptr),但我仍然建议把std::shared_ptrstd::out_ptr一起使用看做一种坏味道,尽量避免这种用例。

inout_ptr

inout_ptr的名字比较抽象,但只是在out_ptr的基础上加了个“in”而已。它会返回一个std::inout_ptr_t类型的对象,函数签名如下:

#include <memory>template< class Pointer = void, class Smart, class... Args >
auto inout_ptr( Smart& s, Args&&... args );

这个“in”是指使用output parameter的函数在重新设置参数的值之前会先使用他们,因此这些函数的特点是:

  1. 非常在乎output parameter里有什么值,根据这些值执行不同的操作
  2. 函数调用期间完全享有output parameter和其资源的所有权
  3. 函数返回后output parameter不变或者被设置为新值

还是看例子,我们对Data增加一个update_data函数,如果name是recreate则删除原来的对象重新创建一个:

int update_data(Data **data)
{if (data == nullptr || *data == nullptr)return 1;if ((*data)->name == "recreate") {delete *data;*data = new Data{"apocelipes"};return 2; // 代表已修改}return 0;
}

现实中没人这么写代码,但存在很多类似的c接口,而且我们也很难控制第三方库的代码质量,难免不会遇上类似的东西。如果想在这种接口上用智能指针,那只能说有福了:

auto resource = std::make_unique<Data>("recreate");Data *ptr = resource.get();
resource.release(); // 释放所有权,但不释放资源
if (auto code = update_data(&ptr); code == 1)std::cerr << "error\n";
else if (code == 2) {resource.reset(ptr);std::cout << "updated, name: " << resource->name << "\n";
} else {resource.reset(ptr);std::cout << "updated, name: " << resource->name << "\n";
}

可以看到代码会变得很复杂,而且一但忘记使用reset就会内存错误。这时候我们就需要inout_ptr帮忙了。

inout_ptr整体上和out_ptr差不多,都是让出资源的所有权然后重新把函数返回的值设置回去,但还有几个差异:

  1. 前面说过需要inout_ptr的函数是需要参数的值的,因此构造inout_ptr_t时之后放弃资源的所有权,不会像out_ptr那样释放资源本身
  2. 资源的释放是调用的函数的责任,inout_ptr只会把函数返回出来的值重新设置回智能指针

inout_ptr改写后的代码如下:

auto resource = std::make_unique<Data>("recreate");if (auto code = update_data(std::inout_ptr(resource)); code == 1)std::cerr << "error\n";
else if (code == 2) {std::cout << "updated, name: " << resource->name << "\n";
} else {std::cout << "updated, name: " << resource->name << "\n";
}

代码看起来清爽多了。

另外虽然inout_ptr也有变长参数,但标准明确规定它不能配合std::shared_ptr使用,这些参数std::unique_ptr用不上,是预留给其他的第三方的类似指针对象使用的。

注意事项

除了std::shared_ptr配合out_ptr使用时需要传入deleter,还有一个注意事项。

两个适配器都不建议这么用:

auto out = std::out_ptr(resource);
func(out);

因为他们都是在析构函数里重新设置智能指针的值,如果绑定到一个局部变量或者其他存储器的变量上,函数调用结束就无法把正确的值重新设置回智能指针,这会导致严重的内存错误。

唯一建议的用法是直接使用out_ptrinout_ptr的返回值:func(std::out_ptr(resource)),这样函数调用结束后表达式结束,返回值作为表达式中创建的临时变量会被析构,这样智能指针的值就被正常设置了。

尽管只要在转换操作符上加上一点限制就能避免误用,但标准考虑到了各种边缘情形,最终没有添加限制,所以我们只能牢记这条注意事项避免踩坑了。

总结

说实话这两个适配器有很浓的给c库函数擦屁股的意味,甚至标准文档上直接拿fopen_s做例子了,我们看下它的函数声明就能秒懂:errno_t fopen_s( FILE *restrict *restrict streamptr, const char *restrict filename, const char *restrict mode );

另外这两个适配器虽然叫智能指针适配器,但也可以对普通裸指针使用,不过我不推荐这种用法。

最后虽然它们的用法都比较偏,但真要用的时候还都有用,所以了解一下总是没坏处的。而且它们的源代码也很简单,有兴趣可以看看libcxx的实现,虽然相比其他家的有点啰嗦,但可读性很强:

out_ptr: https://github.com/llvm/llvm-project/blob/main/libcxx/include/__memory/out_ptr.h

inout_ptr: https://github.com/llvm/llvm-project/blob/main/libcxx/include/__memory/inout_ptr.h

参考资料

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/n4950.pdf p.643

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

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

相关文章

2025年服务贴心的离婚财产分割律师实力推荐榜

2025年服务贴心的离婚财产分割律师实力推荐榜 在婚姻关系破裂时,财产分割往往是当事人最关心的问题之一。选择一位专业、经验丰富且服务贴心的离婚财产分割律师,能够帮助当事人高效、公正地解决纠纷,最大限度地维护…

2025年口碑好的陕西白水苹果精选优质产区

2025年口碑好的陕西白水苹果精选优质产区陕西白水苹果产业概况陕西白水县位于渭北黄土高原,是世界公认的苹果最佳优生区之一。这里海拔适中、昼夜温差大、光照充足、土壤肥沃,为苹果生长提供了得天独厚的自然条件。白…

2025年专业的专利评估顶尖服务推荐

2025年专业的专利评估顶尖服务推荐 在当今高度竞争的商业环境中,专利作为企业核心竞争力的重要组成部分,其价值评估与运营管理显得尤为关键。专业的专利评估服务不仅能帮助企业准确衡量专利资产的市场潜力,还能为技…

P1315 [NOIP 2011 提高组] 观光公交

P1315 [NOIP 2011 提高组] 观光公交 题解题目传送门 我的博客-欢迎来访 个人认为是个好题。另外,推荐一下这篇题解,看着这篇题解学明白的。首先,我们要求的是最小的 \(\sum\limits_{i=1}^{m}{arr_{to_i}}-tim_{i}\)…

2025年11月销量第一认证机构评测:资质认证与实战案例深度剖析

在当今竞争激烈的市场环境中,企业越来越需要借助权威的第三方认证来证明自身的市场地位。销量第一认证作为一种重要的市场背书工具,能够帮助企业提升品牌公信力、增强市场竞争力。对于正在寻找认证机构的企业决策者来…

最近笔记

VSCode json 快捷键 下载Prettify JSON 然后 ctrl+shift +p 然后搜索Prettify JSON 可以直接把文件展开

CH5xx 复位启动时间

CH5xx 复位启动时间本文实测了CH573 CH582 CH592 CH585 CH572系列芯片的复位启动时间(从复位发生到运行main函数) 一.上电复位 上电复位典型值15ms+boot启动文件约40ms+boot执行约40ms+用户程序启动文件约40ms 最长约…

2025年质量好的马靴劳保鞋推荐TOP品牌厂家

2025年质量好的马靴劳保鞋推荐TOP品牌厂家 行业概述 在工业生产、建筑施工、油田作业等高风险环境中,一双高质量的马靴劳保鞋是保障劳动者安全的基础装备。优质的劳保鞋不仅需要具备防砸、防刺穿、防静电等基础功能…

2025年知名的青年鸡热门品牌

2025年知名的青年鸡热门品牌 随着养殖业的快速发展,青年鸡市场逐渐成为行业关注的焦点。优质的青年鸡供应商不仅能提供健康的鸡苗,还能帮助养殖户提高生产效率和经济效益。2025年,市场上涌现出一批优秀的青年鸡品牌…

等离子清洗机设备:安全性高、技术强、外观美观

在精密制造领域,等离子清洗机是提升产品良率与可靠性的隐形基石。企业选择时往往聚焦三大核心需求:设备安全性、技术专业性与外观实用性。以下结合行业评测与用户反馈,推荐2025年五大等离子清洗机品牌,为企业选购提…

红楼梦龄官病死前和贾蔷同居的情节

癸本《红楼梦》中的“松批”关于龄官与贾蔷同居直至病死的这条批语,确实是近年来红学考据中一个非常震撼的发现。它就像一把钥匙,不仅润滑了龄官这个人物的命运齿轮,更将其纳入了《红楼梦》最核心的悲剧叙事之中——…

2025年口碑好的盐城短视频策划精选优质榜

2025年口碑好的盐城短视频策划精选优质榜 在数字化营销浪潮中,短视频已成为企业品牌传播的核心阵地。盐城作为长三角经济圈的重要城市,涌现出一批专注于短视频策划与运营的优质服务商。本榜单基于客户口碑、服务案例…

2025年江西出入口智能设备企业口碑TOP5推荐,江西奇仕盾科技有限公司

在智慧城市建设加速推进的2025年,出入口智能设备与智慧系统解决方案成为政企单位数字化转型的核心刚需。面对市场上鱼龙混杂的供应商,如何选择口碑好、服务优、前景广的合作伙伴?以下结合用户关心的江西奇仕盾科技有…

2025年老人/青少年/成人乳胶枕品牌排行榜,哪个乳胶枕品牌的质量好?

2025年国人睡眠健康意识持续觉醒,老人、青少年、成人等不同年龄段人群对乳胶枕的需求呈现差异化爆发态势——老人关注支撑性与健康安全性,青少年侧重护颈与生长适配性,成人则追求舒适与颈椎减负效果。然而当前乳胶枕…

2025年工业探伤铅房厂家权威推荐榜单:探伤铅房/移动铅房/牙科铅房源头厂家精选

在航空航天、石化装备、压力容器等高端制造领域,工业探伤铅房作为射线防护的核心设施,其防护合规性与结构可靠性直接关系到检测安全与运营效率。随着GBZ/T 180-2006等国家标准的严格执行,以及制造业对智能化、定制化…

2025年有实力涂装喷砂房厂家推荐及选择指南

2025年有实力涂装喷砂房厂家推荐及选择指南 随着工业制造技术的不断升级,涂装喷砂房作为表面处理的核心设备,其性能与质量直接影响生产效率和产品品质。2025年,选择一家技术领先、服务完善的涂装喷砂房厂家至关重要…

跳石头:求最大的最短距离(p2678)

P2678 [NOIP 2015 提高组] 跳石头 题目背景 NOIP2015 Day2T1 题目描述 一年一度的“跳石头”比赛又要开始了! 这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点…

day22-streamlit+agent sdk融合

Streamlit 简介 什么是streamlit Streamlit是一个免费的开源框架,用于快速构建和共享漂亮的数据科学Web应用程序。它是一个基于Python的库,专为机器学习工程师设计。数据分析工程师不是网络开发人员,他们对花几周时…

格式化 U 盘,并还原分区

U 盘因为使用了单片机来格式化,导致接入 linux 开发板的时候,没有 /dev/sda1 分区; 可以按照以下步骤来还原: 1. 以管理员身份运行命令提示符 2. 输入 diskpart 按 enter 3. 输入 list disk 按 enter4. 输入select…

2025 年景观设计公司最新推荐榜:聚焦全流程服务与创新实力,庭院 / 民宿 / 生态园等场景优选品牌清单

引言 当前景观设计行业需求持续升级,从私家庭院到商业文旅项目,客户对生态性、艺术性与落地性的要求不断攀升,但行业仍存在设计同质化、服务断层、方案落地率低等问题。为提供权威参考,行业协会联合专业测评机构开…