std::move 如何使原始变量的值无效?

ar2*_*015 2 c++ move move-semantics c++11 stdmove

cpp 参考中的以下示例中:

#include <iostream>
#include <utility>
#include <vector>
#include <string>

int main()
{
    std::string str = "Hello";
    std::vector<std::string> v;

    // uses the push_back(const T&) overload, which means 
    // we'll incur the cost of copying str
    v.push_back(str);
    std::cout << "After copy, str is \"" << str << "\"\n";

    // uses the rvalue reference push_back(T&&) overload, 
    // which means no strings will be copied; instead, the
    // Contents of str will be moved into the vector.  This is
    // less expensive, but also means str might now be empty.
    v.push_back(std::move(str));
    std::cout << "After move, str is \"" << str << "\"\n";

    std::cout << "The contents of the vector are \"" << v[0]
              << "\", \"" << v[1] << "\"\n";
}
Run Code Online (Sandbox Code Playgroud)

使用std::move可能会导致原始值丢失。对我来说,它看起来像

v.push_back(std::move(str))
Run Code Online (Sandbox Code Playgroud)

导致v[1]创建一个新成员。然后,

&v[1] = &str
Run Code Online (Sandbox Code Playgroud)

但是为什么要破坏 中的值str呢?它没有任何意义。

有很多复杂的教程std::move比我自己的问题更难理解。

任何人都可以写

v.push_back(std::move(str))
Run Code Online (Sandbox Code Playgroud)

在其等效使用c++03?

我找他们的理解是很容易解释,不包含先决条件,如x-valuestatic_castremove_reference,等,因为他们自己需要了解std::move的第一。请避免这种循环依赖。

这些链接也不能回答我的问题:7510182 , 3413470

因为我有兴趣知道如何str伤害而不是会发生什么v[1]

伪代码也很受欢迎,只要它像c++03.


更新:为了避免复杂化,让我们考虑一个更简单的例子int如下

int x = 10;
int y = std::move(x);
std::cout << x;
Run Code Online (Sandbox Code Playgroud)

t.n*_*ese 6

根据实现,这std::move 可能是内部存储器地址的简单交换。

如果您在http://cpp.sh/9f6ru上运行以下代码

#include <iostream>
#include <string>

int main()
{
  std::string str1 = "test";
  std::string str2 = "test2";

  std::cout << "str1.data() before move: "<< static_cast<const void*>(str1.data()) << std::endl;
  std::cout << "str2.data() before move: "<< static_cast<const void*>(str2.data()) << std::endl;

  str2 = std::move(str1);
  std::cout << "=================================" << std::endl;

  std::cout << "str1.data() after move: " << static_cast<const void*>(str1.data()) << std::endl;
  std::cout << "str2.data() after move: " << static_cast<const void*>(str2.data()) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

您将获得以下输出:

str1.data() before move: 0x363d0d8
str2.data() before move: 0x363d108
=================================
str1.data() after move: 0x363d108
str2.data() after move: 0x363d0d8
Run Code Online (Sandbox Code Playgroud)

但结果可能会因编译器和 std 库的实现而异。

但是实现细节可能更复杂http://cpp.sh/6dx7j。如果您查看您的示例,那么您将看到为字符串创建副本不一定需要为其内容分配新内存。这是因为几乎所有操作std::string都是只读的或需要分配内存。所以实现可以决定只做浅拷贝:

#include <iostream>
#include <string>
#include <vector>

int main()
{
  std::string str = "Hello";
  std::vector<std::string> v;

  std::cout << "str.data() before move: "<< static_cast<const void*>(str.data()) << std::endl;

  v.push_back(str);
  std::cout << "============================" << std::endl;
  std::cout << "str.data()  after push_back: "<< static_cast<const void*>(str.data()) << std::endl;
  std::cout << "v[0].data() after push_back: "<< static_cast<const void*>(v[0].data()) << std::endl;

  v.push_back(std::move(str));
  std::cout << "============================" << std::endl;

  std::cout << "str.data()  after move: "<< static_cast<const void*>(str.data()) << std::endl;
  std::cout << "v[0].data() after move: "<< static_cast<const void*>(v[0].data()) << std::endl;
  std::cout << "v[1].data() after move: "<< static_cast<const void*>(v[1].data()) << std::endl;
  std::cout << "After move, str is \"" << str << "\"\n";


  str = std::move(v[1]);
  std::cout << "============================" << std::endl;
  std::cout << "str.data()  after move: "<< static_cast<const void*>(str.data()) << std::endl;
  std::cout << "v[0].data() after move: "<< static_cast<const void*>(v[0].data()) << std::endl;
  std::cout << "v[1].data() after move: "<< static_cast<const void*>(v[1].data()) << std::endl;
  std::cout << "After move, str is \"" << str << "\"\n";
}
Run Code Online (Sandbox Code Playgroud)

输出是

str.data() before move: 0x3ec3048
============================
str.data()  after push_back: 0x3ec3048
v[0].data() after push_back: 0x3ec3048
============================
str.data()  after move: 0x601df8
v[0].data() after move: 0x3ec3048
v[1].data() after move: 0x3ec3048
After move, str is ""
============================
str.data()  after move: 0x3ec3048
v[0].data() after move: 0x3ec3048
v[1].data() after move: 0x601df8
After move, str is "Hello"
Run Code Online (Sandbox Code Playgroud)

如果你看看:

#include <iostream>
#include <string>
#include <vector>

int main()
{
  std::string str = "Hello";
  std::vector<std::string> v;

  std::cout << "str.data() before move: "<< static_cast<const void*>(str.data()) << std::endl;

  v.push_back(str);
  std::cout << "============================" << std::endl;
  str[0] = 't';
  std::cout << "str.data()  after push_back: "<< static_cast<const void*>(str.data()) << std::endl;
  std::cout << "v[0].data() after push_back: "<< static_cast<const void*>(v[0].data()) << std::endl;

}
Run Code Online (Sandbox Code Playgroud)

然后你会假设这str[0] = 't' 只会替换原地的数据。但这不一定是http://cpp.sh/47nsy的情况。

str.data() before move: 0x40b8258
============================
str.data()  after push_back: 0x40b82a8
v[0].data() after push_back: 0x40b8258
Run Code Online (Sandbox Code Playgroud)

和移动原语,如:

void test(int i) {
  int x=i;
  int y=std::move(x);
  std::cout<<x;
  std::cout<<y;
}
Run Code Online (Sandbox Code Playgroud)

大部分会被编译器完全优化:

  mov ebx, edi
  mov edi, offset std::cout
  mov esi, ebx
  call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
  mov edi, offset std::cout
  mov esi, ebx
  pop rbx
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int) # TAILCALL
Run Code Online (Sandbox Code Playgroud)

两者都std::cout 使用相同的寄存器,xy完全优化掉了。


Ded*_*tor 6

std::move是一个简单的强制转换为右值引用。它实际上没有任何事情。

所有的魔法都发生在接收这样一个右值引用的函数中,如果它们接受它作为一个右值引用。他们将其视为无情掠夺这些物品的许可,从而无需实际分配资源,否则就需要进行繁重的复制工作。交换源和目标以避免在分配时甚至需要清理。

因此,使用移动语义通常更有效(相信我可以做得更慢),并且不太可能抛出异常(资源获取容易失败),因为浪费了源代码。

所有这一切都是由被称为 的小型伪装演员启用的std::move,它本身不做任何事情。

  • 尽管他们可以自由无情地掠夺,但他们也必须让尸体处于有效但未指明的状态。未指定的状态是未指定的——不必等同于初始化状态;但确实需要优雅地可破坏。其中,作为一个实现细节,可能只是设置一个 bool 标志来表示“我被掠夺了!”。 (3认同)