news 2026/7/2 1:59:12

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
首先从功能最简单的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";
else
std::cout << "success, name: " << resource->name << "\n";
}

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

$ clang++ -std=c++23 test.cpp
In 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 here
93 | 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 here
19 | 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 (auto err = get_data("apocelipes", std::out_ptr(resource)); err != 0)
+ if (auto err = get_data("apocelipes", std::out_ptr(resource, std::default_delete<Data>{})); err != 0)
std::cerr << "error\n";
else
std::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接口,而且我们也很难控制第三方库的代码质量,难免不会遇上类似的东西。如果想在这种接口上用智能指针,那只能说有福了:

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/2 1:58:57

算法札记:C++ __int128

老纸不想写高精度故学了下__int128_int128 (有符号)‌_‌类型&#xff1a;128位有符号整数最大能存&#xff1a;约 170,141,183,460,469,231,731,687,303,715,884,105,727 (约 1.7 10⁸)十进制大约能存 39 位数。‌不过&#xff0c;用的时候有两点要特别注意&#xff1a;‌‌它…

作者头像 李华
网站建设 2026/7/2 1:57:00

EM3080-W与PIC32MZ的嵌入式条形码解码系统设计

1. EM3080-W与PIC32MZ的条形码解码系统设计背景在零售仓储、物流分拣和工业自动化领域&#xff0c;条形码识别系统的响应速度和准确率直接决定了整体作业效率。传统方案通常采用通用摄像头软件解码的方式&#xff0c;存在功耗高、延迟大&#xff08;普遍在200-300ms&#xff09…

作者头像 李华
网站建设 2026/7/2 1:54:50

WebAssembly 跨语言互操作:数据传递成本比想象中更重要

WebAssembly 跨语言互操作&#xff1a;数据传递成本比想象中更重要 一、互操作成本常常藏在边界上 WebAssembly 的跨语言互操作让 Rust、C、C 等语言可以在浏览器或宿主环境中被 JavaScript 调用。但互操作并不是零成本。WASM 和宿主之间传递字符串、数组和复杂对象时&#xff…

作者头像 李华
网站建设 2026/7/2 1:50:16

Qt-摄像头捕获画面

在qt中捕获摄像头画面&#xff0c;在ui界面上添加一个comboBox控件、label标签和两个pushButton按钮&#xff0c;comboBox用于显示摄像头的设备&#xff0c;按钮用于开启摄像头和捕获当前帧的画面&#xff0c;label用于显示摄像头捕获的画面。//需要在.pro文件中加上multimedia…

作者头像 李华