如何理解C++23中的std::inout_ptr与std::out_ptr?
核心解决的痛点
我们写C++代码经常会调用C风格接口,这类接口有个非常典型的设计:用二级指针作为输出/输入输出参数,返回动态分配的内存或者句柄,比如:
// 示例C接口:分配Widget对象,通过out参数返回指针,返回值为错误码 int c_alloc_widget(size_t count, Widget** out); // 示例C接口:重分配Widget对象,会读取原有指针做释放/重分配,再写入新指针 int c_realloc_widget(Widget** inout, size_t new_count);
在C++23之前,如果我们要把返回的裸指针直接交给智能指针管理,需要多写一个临时裸指针变量,不仅啰嗦,还可能因为中途异常、提前返回等问题导致内存泄漏:
// C++23之前的写法 Widget* raw = nullptr; int err = c_alloc_widget(10, &raw); if (err != 0) { // 错误处理,这里如果忘记free就会泄露 return err; } // 手动转移给智能指针 std::unique_ptr<Widget, decltype(&free)> ptr(raw, free);
std::out_ptr和std::inout_ptr就是为了消除这个冗余流程而生的包装器。
二者的区别与用法
std::out_ptr :纯输出参数场景
适用于C接口只会写入二级指针指向的内容、不会读取原有值的场景,直接把智能指针包在std::out_ptr里传给接口即可,不需要临时变量:
std::unique_ptr<Widget, decltype(&free)> ptr(nullptr, free); int err = c_alloc_widget(10, std::out_ptr(ptr)); if (err == 0) { // 直接用ptr即可,内存已经自动接管 }
实现逻辑:std::out_ptr返回一个临时的std::out_ptr_t对象,内部会先重置你传入的智能指针(释放原有资源),再把自己内部持有的临时裸指针的地址传给C接口。等表达式执行完毕、临时对象析构时,会自动把C接口写入的新指针赋值给你传入的智能指针,完成资源接管。
std::inout_ptr:读写参数场景
适用于C接口会先读取二级指针指向的原有值(比如拿去释放、重分配),再写入新值的场景,用法和out_ptr一致:
// 此时ptr已经持有10个Widget的资源 int err = c_realloc_widget(std::inout_ptr(ptr), 20); if (err == 0) { // ptr现在持有20个Widget的资源,原有资源已经被C接口正确处理 }
实现逻辑:和out_ptr不同,std::inout_ptr构造时不会重置智能指针,而是先把智能指针持有的原有裸指针赋值给内部的临时变量,再把临时变量的地址传给C接口。接口执行完毕后,临时对象析构时再把C接口修改后的新指针赋值回智能指针,不会重复释放原有资源。
关于_t后缀类型
std::out_ptr_t和std::inout_ptr_t是两个工厂函数std::out_ptr、std::inout_ptr的返回值类型。普通业务开发不需要手动声明这类变量,直接用工厂函数生成临时对象即可。只有当你需要适配自定义智能指针时,才需要针对这两个类型做自定义特化。
注意事项
- 两个包装器返回的临时对象只能在当前调用表达式中使用,不能存到变量里后续使用,否则会触发悬垂引用问题。
- 支持所有符合标准的智能指针(
std::unique_ptr、std::shared_ptr等),自定义智能指针只要实现了reset()、get()接口就可以直接适配。 - 会保留智能指针原有的自定义删除器,不需要额外配置。
完整示例
#include <memory> #include <cstdlib> #include <iostream> // 模拟C风格分配接口 extern "C" int alloc_int(int init_val, int** out) { *out = (int*)malloc(sizeof(int)); if (!*out) return -1; **out = init_val; return 0; } // 模拟C风格重分配接口 extern "C" int realloc_int(int** inout, int new_val) { free(*inout); *inout = (int*)malloc(sizeof(int)); if (!*inout) return -1; **inout = new_val; return 0; } int main() { std::unique_ptr<int, decltype(&free)> p(nullptr, free); // out_ptr 示例 int err = alloc_int(42, std::out_ptr(p)); if (err == 0) { std::cout << *p << std::endl; // 输出 42 } // inout_ptr 示例 err = realloc_int(std::inout_ptr(p), 100); if (err == 0) { std::cout << *p << std::endl; // 输出 100 } return 0; }
内容的提问来源于stack exchange,提问作者cbuchart

