可能失败的 unique_ptr 移动的函数签名?

Function signature for potentially failed unique_ptr move?

假设我正在编写一个接受 unique_ptrenqueue() 函数,但我只想在入队 returns 成功时声明其所有权。如果队列已满,我想保持 unique_ptr 不变(用户可以稍后使用相同的项目重试)

bool enqueue(std::unique_ptr&& item){
  if(!vector.full()){
    vector.emplace(std::move(item));
    return true;
  }
  return false;
}
// usage
auto item_ptr = make_unique<>();
while(!enqueue(std::move(item_ptr))){
// item_ptr is not moved
}

我也可以定义函数来取而代之的是左值引用

bool enqueue(std::unique_ptr& item)
while(!enqueue(item_ptr)){
// item_ptr is not moved
}

我不确定该选哪个,它们似乎都有点反模式,因为通常 std::move 表示删除 unique_ptr(大多数时候,我使用的函数是unique_ptr 按值计算),也许有更好的解决方案?

您可以从您的函数中 return unique_ptr。如果失败了return原来的。如果成功 return 一个空 unique_ptr.

那么你可以这样称呼它:

#include <iostream>
#include <memory>
#include <vector>

int counter = 10;

template <typename T> std::unique_ptr<T> enqueue(std::unique_ptr<T> p) {
  if (--counter == 0)
    return std::unique_ptr<T>();
  return p;
}

int main() {
  auto item_ptr = std::make_unique<int>(8);
  while (item_ptr = enqueue(std::move(item_ptr))) {
    std::cout << "looping\n";
  }
  std::cout << "moved\n";
  return 0;
}

其实,我最好去试试这个。是的,我的第一个想法有一个错误。从 while 循环中删除 !。总是测试。 :-)

具有 RValue 引用的第一个版本接受一个临时的。 因此,如果移动没有发生,您将在临时 std::unique_ptr 中拥有一个实例,该实例将很快被删除。 这就是让我不确定它是否是一个好的选择(或令人惊讶的效果)的原因。

起码,大家应该知道这一点。

关于问题

what happens if you do while (!enqueue(make_unique<>()))...

我做了一个 MCVE(只是为了确定):

#include <memory>
#include <iostream>

struct Test {
  Test() { std::cout << "Test::Test()\n"; }
  ~Test() { std::cout << "Test::~Test()\n"; }
};

bool enqueue(std::unique_ptr<Test>&& item)
{
  return false;
}

int main()
{
  for (int i = 0; i < 3 && !enqueue(std::make_unique<Test>()); ++i);
}

输出:

Test::Test()
Test::~Test()
Test::Test()
Test::~Test()
Test::Test()
Test::~Test()

Demo on coliru

如果它旨在防止“滥用”并确保不为临时调用 enqueue(),则可以删除 RValue 版本。

关于 std::move 我喜欢的心态是,只有我表示我可以接受正在修改的对象。这不是导致删除,而是允许可能破坏性的事情发生。

我觉得你的第一个选项没问题。调用者必须通过用 std::move 标记其左值来明确授予权限,并且他们可以通过检查函数的 return 值来检查是否确实发生了某些事情。所以他们不会让他们关心的对象突然消失。这很好 够了 IMO.

但是就像说的,这个版本也提供了传递临时unique_ptr的选项。如果那种情况下毫无意义的工作让您感到困扰,那么我们或许应该换一种方法。

我有时喜欢从 Google 样式指南中获取的一条准则是 [input/]output 参数由非拥有原始指针传递。这基本上将它们限制为左值,同时保留允许修改它们所需的明确性。所以你可以这样做:

bool enqueue(std::unique_ptr<T>* item){
  if(!vector.full()){
    vector.emplace(std::move(*item));
    return true;
  }
  return false;
}

// usage
auto item_ptr = make_unique<T>();
while(!enqueue(&item_ptr)){
// item_ptr is not modified
}

在这样的风格指南下,寻址运算符 & 的作用与 std::move 相同。这是我们告诉功能:“继续,如果需要修改它。”

这个解决方案对我来说似乎有点棘手,因为你正在通过 unique_ptr,但它可能会或可能不会被声明,因此需要以某种方式处理这种行为:比如,返回成功时为空 unique_ptr 或 error/success 代码,进行冗余或难以理解的检查等。我认为你可以做的是在 full() 方法或类似方法的帮助下检查你的队列是否有外部空闲 space。那么您的代码将如下所示:

if(!full()) {
    enqueue(std::move(...));
}