【问题标题】:Why doesn't std::move() of unique_ptr from list<unique_ptr> really move it?为什么 list<unique_ptr> 中 unique_ptr 的 std::move() 没有真正移动它?
【发布时间】:2014-03-01 09:01:30
【问题描述】:
using Ptr = std::unique_ptr<int>;

Ptr f(bool arg) {
  std::list<Ptr> list;
  Ptr ptr(new int(1));
  list.push_back(std::move(ptr));

  if (arg) {
    Ptr&& obj1 = std::move(list.front());
    // Here |obj1| and |list.front()| still point to the same location!
    list.pop_front();
    return std::move(obj1);
  }
  else {
    Ptr obj2 = std::move(list.front());
    list.pop_front();
    return obj2;
  }
};

Ptr&& ptr1 = f(true);   // |ptr1| is empty.
Ptr&& ptr2 = f(false);  // |ptr2| is fine.

完整来源是here

我不明白——为什么obj1list.front() 在调用std::move() 之后仍然指向同一个位置?

【问题讨论】:

  • 你为什么使用这么多的右值引用?如果你使用Ptr obj1 = 而不是Ptr&amp;&amp; obj1 =,一切都应该没问题。
  • @nosid 我有一个模糊的解释。我正在尝试使用 libc++ 中的std::experimental::optional - 而不是Ptr。我的 optional 包含成对的 unique_ptrs 和仅移动对象(即没有默认 ctor)。变体 Ptr obj2 = 甚至无法编译 - 但 Ptr&amp;&amp; obj1 = 可以。然后我对我在问题中解释的情况感到惊讶。当然,我应该检查 optional 本身的问题,但先弄清楚最简单的事情会更容易。
  • 单独考虑表达式std::move(list.front())。如果std::move 确实移动了它的参数(提示:它没有),那么该对象究竟会被移动到哪里
  • @FredOverflow 我的猜测 - 在正常情况下,它应该移动到临时对象,如 Ptr(),当离开范围时会被销毁。但是由于右值引用 - 它应该留得更长一点。
  • 其实,你的猜测听起来不无道理,但事实并非如此。

标签: c++ c++11 move-semantics unique-ptr rvalue-reference


【解决方案1】:

您有一个对std::unique_ptr 内部list引用

Ptr&& obj1 = std::move(list.front());
// ^^ THIS

所以当你这样做时

list.pop_front();

list 中的唯一指针被销毁,剩下的是对已销毁的某个对象的悬空引用,该对象非法通过从函数返回时使用。 p>

更新: std::move 实际上并没有移动 std::unique_ptr。如果我们看到how std::move is defined,我们会看到它只返回被移动表达式的右值引用。当您对其进行测试时,它会变得很有趣:

Ptr&& obj1 = std::move(list.front());
assert(obj1.get() == list.front().get());

您的 else 子句看起来确实不错,您应该在其余代码中使用该模式。

Ptr obj2 = std::move(list.front());
list.pop_front();  // OK; destroys the moved-from unique_ptr
return obj2;       // OK; obj2 points to something valid and is not a dangling reference

@nosid has summed-up what I want this answer to mean.

【讨论】:

  • std::move 不移动,它在当前表达式中将某些内容标记为可移动。
【解决方案2】:

没有移动语义std::unique_ptr 的用处不大。使用移动语义它允许将所有权转移到另一个对象

使用list.push_back(std::move(ptr));,您将数据的所有权转移到一个新元素,并使ptr 处于nullptr 状态(read here)。

之后,如果 arg 为真,由于 list.front() returns a reference to the first element in the containerstd::move 从中取出一个 r 值并将其提供给 r 值引用 obj1。请注意,您并没有将所有权转移给另一个对象,因为您只是要求对数据进行 r 值引用。 r 值引用归根结底是对值的引用

在上面的具体情况下,关于 cout 语句,就相当于得到了一个简单的对象引用

Ptr& obj1 = list.front();

现在您将元素都放入列表中,并且 obj1 r-value 引用“指向”相同的数据,修改一个将导致同时修改两者

Ptr&& obj1 = std::move(list.front());
std::cout << obj1.get() << std::endl << list.front().get() << std::endl; // same address
obj1.reset(new int(2));
std::cout << obj1.get() << std::endl << list.front().get() << std::endl; // same other address

如果你在假的情况下这样做

Ptr obj1 = std::move(list.front());

那么您就会将所有权(以及列表对象的 nullptr-ify)转移给 obj1。

如果你按照上面的推理,你也可以意识到这一行

list.pop_front();

销毁 unique_ptr 对象并使右值引用处于未定义状态。你真的不应该归还它(并使用它)。

【讨论】:

    猜你喜欢
    • 2012-12-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-16
    • 2019-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-02
    相关资源
    最近更新 更多