【问题标题】:In c++11 why not right to use moved variable after std::move?在 c++11 中,为什么不能在 std::move 之后使用移动变量?
【发布时间】:2014-08-16 15:01:35
【问题描述】:

因为std::move(v) 只是一个演员像

static_cast<T&&>(v)

其中Tv 的类型。但是为什么移动的变量不是原始对象的reference

例如,当o1被“移动”到o2时,为什么我们不能再访问o2o1.str_str_

#include <string>
#include <iostream>

struct MyClass {
  std::string str_;
  MyClass(MyClass const &) = delete;
  MyClass &operator=(MyClass const &) = delete;
  MyClass(MyClass &&o) : str_(std::move(o.str_)) {}
  MyClass(std::string const &str) : str_(str) {}
};

int main(void) {
  MyClass o1 = MyClass("o1");
  MyClass o2(std::move(o1));
    std::cout << "o1: " << o1.str_ << "\n"
        << "o2: " << o2.str_ << std::endl;
  return 0;
}

输出:

o1: 
o2: o1

更新:

好像当我换了

MyClass(MyClass &&o) : str_(std::move(o.str_)) {}

MyClass(MyClass &&o) : str_(o.str_) {}

输出将是:

o1: o1
o2: o1

所以根本原因是“std::string”移动?但是为什么这会有所不同呢?

【问题讨论】:

  • “通过o2访问str_o1.str_”是什么意思?
  • @WiSaGaN:OP认为main的第二行神奇地使o2变成了变量o1。不过,这不是 C++ 的工作方式。
  • @KerrekSB 你能告诉我为什么o1.str_ 不再是代码块末尾的o1 吗?
  • 因为它已被移出。
  • 回答您关于为什么移动到的变量 (o2) 不是 reference 的问题:引用与移动无关,以及变量的类型(其中 includes 是否是引用)由您声明变量的内容决定(在这种情况下,o2 被声明为MyClass 类型),不是你如何构造它——实际上引用甚至不是真正构造的——即,没有调用构造函数来生成它们(因为它们只是内部的指针,而不是实际的对象)。

标签: c++ c++11 move rvalue-reference


【解决方案1】:

让我们暂时忘记你的课程,以std::string 为例。

std::string s1{"Hello, World!"};  // 1
std::string s2{s1};               // 2
std::string s3{std::move(s1)};    // 3

在第 1 行,您构建了一个 std::string 对象。在第 2 行,您将 复制 s1s2。现在s1s2 都将包含它们自己的字符串"Hello, World!" 的副本。这个复制是由std::string(或std::basic_string&lt;char&gt;)的复制构造函数完成的,它不会修改参数。

basic_string(basic_string const& other);

在第 3 行,您将 移动 s1 的内容到 s3。为此,您首先将s1 转换为std::string&amp;&amp;(这就是std::move 所做的)。由于这种转换,调用现在将匹配std::string 的移动构造函数,而不是像上一行那样的复制构造函数。

basic_string(basic_string&& other) noexcept;

这个构造函数,因为它总是用字符串的右值实例调用,所以可以从参数中窃取资源。因此,在内部,构造函数将简单地复制一些指向由s1 分配的内存的指针 以存储字符串,并设置s1 实例的状态,使其现在为空。因此s3 现在拥有该字符串。

当您在类实例中移动 string 数据成员时,也会发生同样的事情。这就是为什么从std::string 移动的对象在打印时显示为空的原因。

如果std::string 实现使用小字符串优化,执行的操作会有所不同,但这只是一个实现细节。从概念上讲,这两种情况都如上所述。

【讨论】:

  • 还是一头雾水:为什么构造函数要打扰set the state of s1,而不是让它保持原样?
  • @HongxuChen 假设字符串指向堆上的数据。如果移动构造函数没有修复 s1,那么 s3 和 s1 都将指向同一个位置。当 s3 超出范围时,它会释放它指向的数据。当 s1 超出范围时也会发生同样的情况。如果它们指向同一个位置,那就是双重免费错误。
  • @Nevin 所以是构造函数在内部执行的额外操作产生了影响,它与std::move 本身无关,对吧?是否存在保证移动变量(如s1o1)使用正确的场景?
  • @Hongxu std::move 只不过是static_cast&lt;T&amp;&amp;&gt;。通过这样做,您声明您将moved 对象传递给的函数有权获取源对象的内部结构。严格来说,std::string 的移动构造函数不需要将源设置为空字符串,因为标准只说明源处于有效但未指定状态。但如果它没有设置为空,你会遇到像 Nevin 所说的双重删除问题。您可以以不违反该使用的任何先决条件的任何方式使用已移动的 from 变量。
  • @Hongxu 比如检查移出的字符串是否为空是可以的,但是使用operator[]访问元素就不行,因为empty()没有任何前置条件,但是operator[]需要pos &lt;= size(),其中posoperator[] 的参数。
【解决方案2】:

std::move 只是将其参数转换为右值引用,这很正确。

但是任何接收到这样一个右值引用的函数都有明确的许可来掠夺它,以便更有效地完成它的工作,它必须让它处于某种任意的有效状态。

你当然可以继续使用它,只是要知道“一些任意有效状态”的保证是多么的少。

这种对移动对象的掠夺使移动语义成为移动语义,并且是明确的目标。

【讨论】:

  • 对不起,我没有你的想法。我对这个问题进行了更新,请您详细介绍一下这个示例吗?
  • 由于最近 SO 的技术问题,我收到您的消息为时已晚,Praetorian 已经添加了一个答案,其中包含您可能需要的所有示例来解决您的修正问题...
【解决方案3】:

因为大多数移动构造函数的实现确实将数据从一个对象移动到另一个对象,并使第一个对象上的数据无效。

这就是为什么它被称为移动而不是复制。

【讨论】:

    猜你喜欢
    • 2014-01-17
    • 2013-01-07
    • 2016-12-05
    • 1970-01-01
    • 2020-12-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多