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的函数在重新设置参数的值之前会先使用他们,因此这些函数的特点是:
- 非常在乎output parameter里有什么值,根据这些值执行不同的操作
- 函数调用期间完全享有output parameter和其资源的所有权
- 函数返回后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差不多,都是让出资源的所有权然后重新把函数返回的值设置回去,但还有几个差异:
- 前面说过需要
inout_ptr的函数是需要参数的值的,因此构造inout_ptr_t时之后放弃资源的所有权,不会像out_ptr那样释放资源本身 - 资源的释放是调用的函数的责任,
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_ptr和inout_ptr的返回值:func(std::out_ptr(resource)),这样函数调用结束后表达式结束,返回值作为表达式中创建的临时变量会被析构,这样智能指针的值就被正常设置了。
尽管只要在转换操作符上加上一点限制就能避免误用,但标准考虑到了各种边缘情形,最终没有添加限制,所以我们只能牢记这条注意事项避免踩坑了。