【问题标题】:Why do I need to move `std::unique_ptr`为什么我需要移动`std::unique_ptr`
【发布时间】:2021-10-16 13:04:20
【问题描述】:

给定以下代码:

#include <iostream>
#include <memory>

struct A {};

struct B : public A {};

std::pair<bool, std::unique_ptr<B>> GetBoolAndB() {
    return { true, std::make_unique<B>() };
}

std::unique_ptr<A> GetA1() {
    auto[a, b] = GetBoolAndB();
    return b;
}

std::unique_ptr<A> GetA2() {
    auto [a, b] = GetBoolAndB();
    return std::move(b);
}

GetA1 无法编译,出现此错误:

C2440: 'return': cannot convert from 'std::unique_ptr&lt;B,std::default_delete&lt;_Ty&gt;&gt;' to 'std::unique_ptr&lt;A,std::default_delete&lt;_Ty&gt;&gt;'

而GetA2 编译时没有错误。

我不明白为什么我需要调用std::move 才能使该功能正常工作。

编辑

澄清一下,正如 DanielLangr 在 cmets 中指出的那样,我的疑问在于

std::unique_ptr<A> GetA3() {
    std::unique_ptr<B> b2; 
    return b2;
}

无需std::move即可编译和转移所有权。

现在我了解到,在 GetA1 和 GetA2 的情况下,如果使用结构化绑定,b 恰好是某个对象的一部分,因此必须将其移动为右值参考。

【问题讨论】:

  • 我添加另一个观察:如果在GetA1 中添加std::unique_ptr&lt;B&gt; b2; 和return b2; 而不是return b;,则代码编译。所以问题似乎与结构化绑定有关。另一方面,static_assert(std::is_same_v&lt;decltype(b), decltype(b2)&gt;); 通过了,所以我真的不知道返回 b 或另一个相同类型的 lobcal 对象有什么区别。
  • 您是在问为什么std::unique_ptr 需要被移动一般,即为什么它不可复制,或者您是在问为什么它需要被移动这种具体情况?
  • 这样想,唯一的指针表示唯一的所有权。这样的所有者只能有一个。如果你返回一个 unique_ptr,那么你基本上是在说:调用 GetA2 的代码将成为唯一指针的新所有者,你必须移动所有权,因此移动
  • @PepijnKramer 我认为您不理解 OP 要求的问题。如果你写std::unique_ptr&lt;B&gt; b2; return b2;,它将编译和转移所有权,而不需要std::move。我相信 OP 在问为什么它与结构化绑定的工作方式不同。
  • @DanielLangr 哎呀,谢谢。我误读了这个问题。

标签: c++ c++17 unique-ptr move-semantics structured-bindings


【解决方案1】:

我不明白为什么我需要调用 std::move 才能使函数工作。

因为std::unique_ptr对应的构造函数有一个右值引用类型的参数:

template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept;

详见文档:https://en.cppreference.com/w/cpp/memory/unique_ptr/unique_ptr

由于右值引用不能绑定左值,因此,您不能使用b(即左值)作为此构造函数的参数。

如果您想知道为什么b 在return 语句中被视为左值,请参见例如:Why Structured Bindings disable both RVO and move on return statement? 简而言之,b 不是具有自动存储持续时间的变量,而是对一对的引用元素。

错误消息基本上只是说编译器找不到任何可行的转换构造函数,因此它“无法转换...”。

通过使用std::move 调用包装b,您正在创建一个引用与b 完全相同的对象的表达式,但它的类别是右值。这可能与该构造函数参数绑定。

【讨论】:

  • 嗨,你介意阅读我在这个问题下的评论。看起来返回本地左值本身并不是问题。
  • @enlico 这在很大程度上取决于该左值实际上是什么。我用相关帖子的链接更新了答案。注意这里的b不是自动存储时长的变量。
  • 但在我制作的示例中,b 和 b2 具有相同的类型和值类别; static_assert 不能确保这一点吗?编译器对它们两者的行为有何不同?
  • @enlico 因为结构化绑定的decltype 删除了引用,也就是说,它解析为引用的类型:en.cppreference.com/w/cpp/language/structured_binding。
【解决方案2】:

因为任何时候都应该只有一个有效的unique_ptr。 这就是为什么它被称为unique_ptr。

unique_ptr 不可复制,您必须移动它。
否则你最终会得到一个指针的副本,这会破坏它的唯一性!
见:Rules for Smart Pointers

【讨论】:

  • 这里不完全正确。允许编译器在某些情况下执行 NRVO,实际上在返回唯一指针时应避免使用std::move,请参见例如this 问题。正如 Daniel Langr 的回答所指出的,OP 的情况有点特殊。
  • @andreee 请注意,问题根本不在于 NRVO,它始终是可选的。问题在于在某些情况下将return 语句中的命名对象视为右值。如果满足,这是强制性的。 (NRVO 只是说复制/移动构造函数被省略了,而相关规则说应该应用移动构造函数而不是复制构造函数。这两个问题是正交的。)
  • @DanielLangr 我指的是“你必须移动它”wrt。返回一个unique_ptr。人们普遍认为他们总是必须移动,这是一个常见的误解,只是因为它是unique_ptr。如果你只是简单地返回指针,那是不对的。
  • @andreee 我同意。只是想澄清一下 NRVO 的事情。
  • @kenba 我明白了,但一般来说,你不能只根据帖子标题写答案。尽管在这种情况下 OP 可以在那里提到结构化绑定。没有它,标题就太笼统了。
猜你喜欢
  • 1970-01-01
  • 2015-08-12
  • 2011-03-16
  • 1970-01-01
  • 2015-04-15
  • 2016-03-14
  • 1970-01-01
  • 1970-01-01
  • 2016-09-07
相关资源
最近更新 更多