std::unique_ptr::release()和std::move()的区别

10

我有一个代表运行时上下文并构建树的类,树的根节点保存在unique_ptr中。当构建树完成后,我想提取该树。代码如下(不可运行,这不是一个调试问题):

class Context {
    private:
    std::unique_ptr<Node> root{new Node{}};
    public:
    // imagine a constructor, attributes and methods to build a tree
    std::unique_ptr<Node> extractTree() {
        return std::move(this->root);
    }
};

因此,我使用了 std::move() 来提取 Context 实例中的根节点。

然而,使用 std::move() 也有其他替代方法,例如:

std::unique_ptr<Node> extractTree() {
    // This seems less intuitive to me
    return std::unique_ptr<Node>{this->root.release()};
}

std::move()是最好的选择吗?


当您移动 this->root 后,您需要从中调用 reset。最好也阅读一下这个答案 https://dev59.com/RWEi5IYBdhLWcg3w6PvZ#20850223。 - neuront
3
不,你不需要。 - Benjamin Lindley
移动后,src 包含 nullptr,重置是无意义的! - paulm
2
如果你发现自己在 unique_ptr 上调用了 release(),那么这是一个警告,你的逻辑很可能不稳定。 - Richard Hodges
3
除非必须将所有权转移给C API,否则请勿这样做。 - David Haim
显示剩余2条评论
2个回答

12

第一版肯定是更好的选择,因为第二版基本上做了第一版所有的事情,而且代码量更多,可读性更差。

从哲学上讲,第二版只是移动了独占式指针,没有多余的操作。所以,为什么不使用已经存在、更易读、更符合惯用法的std::unique_ptr(std::unique_ptr&&)呢?

最后,如果你的智能指针在将来的某个时候将持有自定义删除器,第一版将确保删除器也会被移动,而第二版则不会移动删除器。我可以想象一个程序员使用release将带有自定义删除器的unique_pointer构建成非自定义删除器的unique_pointer。使用移动语义,程序将无法编译。


2
你现在做的事情很危险。一旦你调用了getTree(),就不应该再次调用它,这在接口中并不清晰。你可能需要重新考虑你的设计(例如使用shared_ptr可能会更好,或者只需将根作为原始指针存储,并手动处理释放)。
总之,如果你想坚持你的设计,使用std::move是两种更好的选择,因为它使你的意图更加明确。
编辑:显然,“must not”在英语中有一种被禁止的特殊含义,我没有意识到。你可以随意多次调用函数,但如果连续调用,它不会返回指向有效对象的指针。

1
第二次调用 getTree() 没有问题。它只会返回一个空的 unique_ptr - Benjamin Lindley
2
为什么不能调用 getTree() 两次?一旦根节点被移动,它就会被设置为 nullptr,因此再次从中移动会得到一个 nullptr,这似乎是一个空对象的非常合理的行为。 - nwp
6
我认为期望get...()方法具有const行为是合理的。 我认为将该函数重命名为extractTree()之类的名称将更清晰地传达这种调用后Context不再具有树结构的事实。 - mindriot
1
@kamikaze:这就是为什么我觉得从这样的函数中获取unique_ptr很令人惊讶。;-) 还要注意的是,我可以执行getTree()->something(),甚至没有意识到我刚刚摧毁了树,想知道为什么语句第二次失败。 - DevSolar
那么是通过使用 std::move 返回时释放根节点的 unique_ptr( unique_ptr&& u ); 构造函数吗? - Zitrax
显示剩余7条评论

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