在接管所有权之前使用std::unique_ptr进行测试

4
我有一个类,基本上是一个队列,用于在2个线程之间传输动态分配的对象。第一个线程创建这些对象,第二个线程消耗它们。我使用“std::unique_ptr”将对象的所有权从线程1传递到线程2。
实际上,调用将对象放入队列的方法如下:
queue.put(std::move(unique_ptr_to_my_object));

以及签名:

bool Queue::put(std::unique_ptr<T> p);

问题在于put()方法必须检查某些条件来决定是否可以将对象添加到队列中。如果条件为false,则该方法仅返回false以指示无法将对象添加到队列中,但是对象已被销毁,因为所有权已由put()获取。

所以我想知道是否可以像这样重写put(),或者是否有更好的解决方案:
bool Queue::put(std::unique_ptr<T> &ref) {
    if(CANNOT_ADD)
        return false; // ownership remains in the calling function
    std::unique_ptr<T> p = std::move(ref); // we know we can add so take ownership
    /* ... */
}

我会将修改后的函数更改为Queue::put(std::unique_ptr<T>&& ref) - NathanOliver
@Tyker 不是这样的。它需要一个对 rvalue 的引用。只有在使用它来分配或初始化某些内容时,它才会被移动。请记住,只有移动构造函数或移动赋值运算符才能实际移动某些内容。 - NathanOliver
1
@NathanOliver 仍然为什么?在这种情况下,您可能确实希望限制为左值引用...否则,如果放置失败,只有一个临时变量留下来。 - Martin Bonner supports Monica
2
@MartinBonner 为什么要将函数限制在lvalue上?如果我从某个函数中获取了一个unique_ptr,并且不太关心它是否被移动到队列中,为什么我需要先将其变成lvalue。 - NathanOliver
@NathanOliver 嗯,这显然取决于整体要求,但通常这些东西并不完全是通用的。鉴于 OP 担心如果 put 失败会丢失对象,我想建议的用法是那种你应该关心它是否移动到队列中 - 如果失败了,你必须做其他事情。不过我同意,如果只有有时候关心对象,那么你的方法是有道理的。 - Martin Bonner supports Monica
1
@James 我认为你最初的解决方案实际上是最好的。在失败的情况下返回对象是违反直觉的,并且使用了倒置逻辑(它实际上返回成功的false和失败的true)。我唯一会做出的改变就是将方法tryPut重命名,以便一眼就能看出它可能会失败。 - Peter Ruderman
2个回答

5

没问题,另一种选择是:

std::unique_ptr<T> Queue::put(std::unique_ptr<T> p) {
    if (CANNOT_ADD)
        return p; // Ownership returned to caller.
    /* ... */
    return {}; // Return empty nullptr to indicate success.
}

您的方法有一个优点,即如果...中的代码抛出异常,调用方仍然保留所有权。

1
我认为这样做更好,因为没有条件的所有权转移,这有点痛苦。 - SergeyA
1
@SergeyA 是的,那也是我的想法,但这真的取决于OP试图解决什么问题。 - Martin Bonner supports Monica
1
我看到的问题是,put() 的用户仍然可能使用不正确。如果他们调用 put() 并且没有对结果做任何处理,则在条件失败时对象将被静默删除。我更喜欢通过引用传递。除非 put() 显式地接受它,否则所有权归所有者所有。 - Martin York

3

您可以将函数的签名更改为:

std::unique_ptr<T> Queue::put(std::unique_ptr<T> p);

因此,如果该函数无法接受该对象,则会返回该指针,否则返回nullptr。另一种解决方案是有条件地拥有所有权:

bool Queue::put(std::unique_ptr<T> &&p);

仅在成功时移出对象。在这种情况下,接受右值引用比左值引用更好,原因至少有两个:

  1. 您仍然可以传递临时对象。
  2. 您需要在调用代码中显式使用 std::move

尽管您可以在 std::move 后继续使用该指针,但这种变体的可读性较差。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接