使用作为参数返回的指针的最佳做法是什么

What is the best practice to consume a pointer returned as an argument

通常,当创建指向对象的指针并从函数返回时,您使用 unique_ptr 来确保它在范围末尾被删除。

CustomType* GetTheObject(); // Let's say this the function signature and it returns a new object

void Main() {
   auto object = make_unique(GetTheObject());
   object->DoSomething();
   // Then the object is deleted automatically
}

如果函数签名是这样的怎么办

bool GetTheObject(CustomType** object);

我可以想象一种相当冗长的消费方式

void Main() {
   // Declare a pointer which we never need
   CustomType* object_ptr;
   if(GetTheObject(&object_ptr)) {
      // Then create a unique_ptr out of it
      auto object = make_unique(object_ptr);
      object->DoSomething();
      // Then the object is deleted automatically
   }
}

在这种情况下,是否有更好的推荐方法来使用对象。我可以考虑另一个实现 & 运算符的 unique_ptr2 class 然后像

一样使用它
unique_ptr2 object;
if(GetTheObject(&object)) {
   // use it
}

是否有现成可用的 unique_ptr2 实现可以做到这一点?感觉还是不太理想。有没有更好的方法?

我认为返回 std::unique_ptr 比返回原始指针更安全,因为返回原始指针有调用代码意外泄漏对象的风险。我建议这样做:

#include <iostream>
#include <memory>

class CustomType
{
   // ...
};

std::unique_ptr<CustomType> GetTheObject()
{
   if ((rand()%2) != 0) return std::make_unique<CustomType>();

   return std::unique_ptr<CustomType>();  // nothing to return, sorry
}

int main(int argc, char ** argv)
{
   if (std::unique_ptr<CustomType> p = GetTheObject())
   {
      std::cout << "Got the object!" << std::endl;
   }
   return 0;
}

如果您不得不忍受一个您不喜欢其形状且无法更改的现有函数,您可以将丑陋隐藏在包装函数中,然后改为调用包装函数:

std::unique_ptr<CustomType> PrettyGetTheObject()
{
   CustomObject * obj;
   if (GetTheObject(&obj)) return std::unique_ptr<CustomObject>(obj);

   return std::unique_ptr<CustomType>();  // nothing to return, sorry
}

一种可能的方法是使用包装函数。示例:

bool GetOriginal(char **pptr);

bool GetWrapped(std::unique_ptr<char> * puptr) {
  bool result;
  if (puptr != nullptr) {
    char * cptr;
    result = GetOriginal(&cptr);
    *puptr = std::make_unique(cptr);
  } else {
    result = GetOriginal(nullptr);
  }
  return result;
}

这假定了传递 null 的常用模式以避免获取管理指针(例如,如果您只对 return 值感兴趣)。

如果将 null 传递给原始函数不是其 api 的一部分,那么您当然可以使用对 std::unique_ptr 的引用而不是原始指针。

如果你有很多这样的函数,你当然也可以为此编写一个通用的包装函数:

template<typename Fn, typename T>
bool wrap(Fn fn, std::unique_ptr<T> * puptr) {
  bool result;
  if (puptr != nullptr) {
    T * cptr = nullptr;
    result = fn(&cptr);
    *puptr = std::make_unique(cptr);
  } else {
    result = fn(nullptr);
  }
  return result;
}

// usage: wrap(GetOriginal, &some_unique_ptr)

如果原始函数需要更多参数,则使用 std::bind 或 lambda。

我可能想编写自动转换 to/from 唯一指针的代码。我们从具有相同签名的现有函数 "automatically" 生成一个新函数,但是 T* return 值是 unique_ptr<T> 并且 T** 参数是 unique_ptr<T>* 参数。

然后我们使用 RAII 和模板元编程注入转换样板文件。

A ptr_filler 是一种 RAII 类型,它将 unique_ptr<T>* 转换为 T**:

template<class T>
struct ptr_filler {
  std::unique_ptr<T>* output = nullptr;
  T* temporary = nullptr;
  ptr_filler( std::unique_ptr<T>* bind ):output(bind) {}
  operator T**()&&{return &temporary;}
  ~ptr_filler() {
    if (temporary)
      *output = std::unique_ptr<T>(temporary);
  }
};

ret_converter_t 执行从 C 风格 API 到 C++ unique-ptr API:

的类型转换
template<class T> struct ret_converter { using type=T; };
template<class T> using ret_converter_t = typename ret_converter<T>::type;
template<class T> struct ret_converter<T*> { using type=std::unique_ptr<T>; };

get_converter_t 将参数类型从 C 风格 API 转换为通过 ptr_filler:

填充唯一指针的类型
template<class T> struct get_converter { using type=T; };
template<class T> using get_converter_t = typename get_converter<T>::type;
template<class T> struct get_converter<T**> { using type=ptr_filler<T>; };

最后,call从你传递给它的函数指针推导出它的参数,然后转换参数和retval以使用唯一的ptr内存管理,并为你调用函数f:

template<class R, class...Args>
ret_converter_t<R> call( R(* f)(Args...), get_converter_t<Args>... args ) {
  return static_cast<ret_converter_t<R>>( f( std::forward<decltype(args)>(args)... ) );
}

现在我们可以:

struct CustomType {
  int x;
};
CustomType* GetTheObject(int x) { return new CustomType{x}; }
bool MakeTheObject( CustomType** pp, int a, int b ) { *pp = new CustomType{a+b}; return a>b; }

我们可以做到:

int main() {
  std::unique_ptr<CustomType> ptr;
  std::cout << call( MakeTheObject, &ptr, 2, 1 ) << " = 1\n";
  std::cout << ptr->x << " = 3\n";
  ptr = call( GetTheObject, 7 );
  std::cout << ptr->x << " = 7\n";
}

您可以使用 call<MakeTheObject> 语法变得更漂亮,但这需要工作。这假设您正在包装的 API 是 C-ish API 但 returns new 对象。

Live example.