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

时间:2018-08-19 09:58:56

标签: c++ c++11 move move-semantics stdmove

在以下来自cpp reference的示例中:

#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";
}

使用std::move可能会导致原始值丢失。在我看来,

v.push_back(std::move(str))

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

&v[1] = &str

但是为什么要破坏str中的值?这没有道理。

关于std::move的教程很复杂,比我自己的问题难理解。

任何人都可以写

v.push_back(std::move(str))

使用c++03等效吗?

我正在寻找一个易于理解的解释,并且不包含x-valuestatic_castremove_reference等先决条件,因为它们本身需要理解std::move第一。请避免这种循环依赖。

这些链接也无法回答我的问题:75101823413470

因为我有兴趣了解str的危害,而不是v[1]会发生什么。

只要c++03一样简单,也欢迎使用伪代码。


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

int x = 10;
int y = std::move(x);
std::cout << x;

2 个答案:

答案 0 :(得分:3)

std::move是一个简单的强制转换为右值引用。实际上并没有做什么

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

因此,使用移动语义通常效率更高(可以做得更快,请相信我),并且抛出异常的可能性也较小(资源获取很容易失败),因为浪费了源代码。 >

所有这些都由名为std::move的小型伪装演员启用,它本身不做

答案 1 :(得分:2)

根据实现的不同,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;
}

您将获得以下输出:

str1.data() before move: 0x363d0d8
str2.data() before move: 0x363d108
=================================
str1.data() after move: 0x363d108
str2.data() after move: 0x363d0d8

但是结果可能会有所不同,具体取决于编译器和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";
}

输出为

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"

如果您看一下:

#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;

}

然后,您将假设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

移动基本体,如:

void test(int i) {
  int x=i;
  int y=std::move(x);
  std::cout<<x;
  std::cout<<y;
}

大部分将由编译器完全优化:

  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

std::cout使用相同的寄存器,xy完全被优化。