std::move 如何使原始变量的值无效?
How does std::move invalidates the value of original variable?
以下来自 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-value
、static_cast
、remove_reference
等先决条件的解释,因为它们本身需要理解 std::move
首先。请避免这种循环依赖。
这些链接也没有回答我的问题:7510182, 3413470
因为我想知道 str
是如何造成伤害的,而不是 v[1]
会发生什么。
也欢迎使用伪代码,只要它像c++03
一样简单。
更新:为避免复杂化,让我们考虑一个更简单的 int
示例,如下所示
int x = 10;
int y = std::move(x);
std::cout << x;
根据具体实现,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
但结果可能因编译器和标准库的实现而异。
但实现细节可能更加复杂 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
使用相同的寄存器,x
和y
完全优化掉了。
std::move
是一个简单的转换为右值引用。它实际上 没有做 任何事情。
所有的魔法都发生在函数中 接收 这样的右值引用,如果他们接受它作为右值引用。他们将其视为 无情地掠夺 那些对象的许可,从而避免了实际分配资源的需要,否则就不需要进行复制的繁重工作。在分配时交换源和目标以避免清理的需要。
因此,使用移动语义通常效率更高(一个人可以做的更快,相信我),并且不太可能抛出异常(资源获取很容易失败),以破坏源为代价。
所有这一切都是由被称为 std::move
的小型伪装演员启用的,它本身 不做 任何事情。
以下来自 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-value
、static_cast
、remove_reference
等先决条件的解释,因为它们本身需要理解 std::move
首先。请避免这种循环依赖。
这些链接也没有回答我的问题:7510182, 3413470
因为我想知道 str
是如何造成伤害的,而不是 v[1]
会发生什么。
也欢迎使用伪代码,只要它像c++03
一样简单。
更新:为避免复杂化,让我们考虑一个更简单的 int
示例,如下所示
int x = 10;
int y = std::move(x);
std::cout << x;
根据具体实现,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
但结果可能因编译器和标准库的实现而异。
但实现细节可能更加复杂 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
使用相同的寄存器,x
和y
完全优化掉了。
std::move
是一个简单的转换为右值引用。它实际上 没有做 任何事情。
所有的魔法都发生在函数中 接收 这样的右值引用,如果他们接受它作为右值引用。他们将其视为 无情地掠夺 那些对象的许可,从而避免了实际分配资源的需要,否则就不需要进行复制的繁重工作。在分配时交换源和目标以避免清理的需要。
因此,使用移动语义通常效率更高(一个人可以做的更快,相信我),并且不太可能抛出异常(资源获取很容易失败),以破坏源为代价。
所有这一切都是由被称为 std::move
的小型伪装演员启用的,它本身 不做 任何事情。