找回密码
 立即注册
首页 业界区 业界 C++23的out_ptr和inout_ptr

C++23的out_ptr和inout_ptr

匣卒 2025-11-5 10:45:04
c++23新增了一些智能指针适配器,用来扩展和简化智能指针的使用。
这次主要介绍的是std:ut_ptr和std::inout_ptr。这两个适配器用法和实现都很简单,但网上的文档都比较抱歉,还缺少一些比较重要的部分,因此单开一篇文章记录一下。
out_ptr

首先从功能最简单的out_ptr讲起。
std:ut_ptr其实是一个函数,返回一个类型为std:ut_ptr_t的智能指针适配器,函数签名如下:
  1. #include <memory>
  2. template< class Pointer = void, class Smart, class... Args >
  3. 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)。
这类函数有几个特点:

  • 不在乎output parameter里有什么值
  • 函数调用期间完全享有output parameter和其资源的所有权
  • 函数返回后output parameter通常被设置为新值
在c++提倡少用裸指针的今天,我们越来越习惯使用shared_ptr和unique_ptr,但不管哪种智能指针都很难直接适配上面这些函数,看个例子就明白了:
  1. int get_data(const std::string &name, Data **retval)
  2. {
  3.     if (!check_name(name)) {
  4.         return ErrCheckFailed;
  5.     }
  6.     *retval = make_data(name);
  7.     return 0;
  8. }
  9. // 使用裸指针
  10. Data *data_ptr = nullptr;
  11. if (auto err = get_data("name", &data_ptr); err != 0) {
  12.     错误处理
  13. } else {
  14.     这里可以使用data_ptr
  15. }
复制代码
使用裸指针的时候代码比较简单,我们再来看看使用智能指针的时候:
  1. std::unique_ptr<Data> resource;
  2. Data *data_ptr = nullptr;
  3. if (auto err = get_data("name", &data_ptr); err != 0) {
  4.     错误处理
  5. } else {
  6.     resource.reset(data_ptr);
  7.     这里可以使用resource
  8. }
复制代码
代码会变得啰嗦,而且如果我们忘记了调用reset,那么资源就可能泄漏了;还有最重要的一点,我们主动使用了裸指针,而这正是我们想避免的。
这时候就需要out_ptr了。out_ptr生成的适配器会先放弃智能指针持有资源的所有权并将旧资源释放,因为如前面所说我们要调用的函数会接管资源的所有权,接着构造出的std:ut_ptr_t有自动的类型转换方法,可以把智能指针转换成我们需要的T**交给函数使用,最后在函数调用结束之后再把新的资源设置回智能指针。
所以上面的例子可以改成:
  1. std::unique_ptr<Data> resource;
  2. if (auto err = get_data("name", std::out_ptr(resource)); err != 0) {
  3.     错误处理
  4. } else {
  5.     这里可以使用resource,无需reset
  6. }
复制代码
除了代码更简洁,out_ptr还保证异常安全,即使在调用get_data的过程中抛出了异常,也不会出现资源泄漏。
利用out_ptr我们可以在使用智能指针的同时兼容老旧接口。
out_ptr和shared_ptr

如果只看函数签名,很多人会觉得out_ptr也可以直接配合std::shared_ptr使用,然而现实是多变的:
  1. struct Data {
  2.     std::string name;
  3. };
  4. int get_data(const std::string &name, Data **retval)
  5. {
  6.     if (name == "")
  7.         return 1;
  8.     *retval = new Data{name};
  9.     return 0;
  10. }
  11. int main()
  12. {
  13.     std::shared_ptr<Data> resource;
  14.     if (auto err = get_data("apocelipes", std::out_ptr(resource)); err != 0)
  15.         std::cerr << "error\n";
  16.     else
  17.         std::cout << "success, name: " << resource->name << "\n";
  18. }
复制代码
报错虽然很长但只要关注前几行就行了,错误的原因很明显,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或者其他有特殊要求的类似智能指针准备的。
好在上面的代码稍作修改就能正常使用:
[code]int main(){    std::shared_ptr resource;-   if (err = get_data("apocelipes", std:ut_ptr(resource)); err != 0)+   if (err = get_data("apocelipes", std:ut_ptr(resource, std::default_delete{})); err != 0)        std::cerr

相关推荐

您需要登录后才可以回帖 登录 | 立即注册