在 c++11 中,为什么不能在 std::move 之后使用移动的变量?

Hon*_*hen 4 c++ move rvalue-reference c++11

就像std::move(v)演员表一样

static_cast<T&&>(v)
Run Code Online (Sandbox Code Playgroud)

T的类型在哪里v?但是为什么移动的变量不是reference原始对象的 a 呢?

例如,当o1被“感动”来o2,我们为什么不能访问str_o2通过o1.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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

o1: 
o2: o1
Run Code Online (Sandbox Code Playgroud)

更新:

似乎当我改变

MyClass(MyClass &&o) : str_(std::move(o.str_)) {}
Run Code Online (Sandbox Code Playgroud)

MyClass(MyClass &&o) : str_(o.str_) {}
Run Code Online (Sandbox Code Playgroud)

输出将是:

o1: o1
o2: o1
Run Code Online (Sandbox Code Playgroud)

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

Pra*_*ian 8

让我们暂时忘记您的课程,仅以std::string一个为例。

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

在第 1 行,您已经构造了一个std::string对象。在第 2 行,您要复制 s1s2. 现在,s1s2都将包含自己的字符串副本"Hello, World!"。这个复制是由std::string(or std::basic_string<char>)的复制构造函数完成的,它不修改参数。

basic_string(basic_string const& other);
Run Code Online (Sandbox Code Playgroud)

在第 3 行,您内容移动s1s3。为了做到这一点,你首先投射s1std::string&&(这是做什么的std::move)。由于这个转换,调用现在将匹配std::string的移动构造函数,而不是像前一行那样的复制构造函数。

basic_string(basic_string&& other) noexcept;
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • @HongxuChen 假设字符串指向堆上的数据。如果移动构造函数没有修复 s1,那么 s3 和 s1 都将指向同一个位置。当 s3 超出范围时,它会释放它指向的数据。当 s1 超出范围时也会发生同样的情况。如果他们指向同一个位置,那就是一个双重免费的错误。 (2认同)
  • @Hongxu`std::move`只不过是`static_cast&lt;T&amp;&amp;&gt;`。通过这样做,您可以声明您传递“移动”对象的函数有权获取源对象的内部结构。严格来说,`std::string` 的移动构造函数不需要将源设置为空字符串,因为标准仅声明源处于*有效但未指定的状态*。但如果它没有设置为空,你就会遇到像 Nevin 所说的双重删除问题。您可以以任何不违反该使用前提条件的方式使用移出的变量。 (2认同)

Ded*_*tor 7

你说得对,std::move只是将其参数转换为右值引用。

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

不过,您当然可以继续使用它,但请注意“某些任意有效状态”的保证是多么少。

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