std::move 其实什么都不移动
名字起得不好是很多困惑的来源。std::move 本身不产生任何指令,它的实现基本就是一个 static_cast:
template <class T>
constexpr std::remove_reference_t<T>&& move(T&& t) noexcept {
return static_cast<std::remove_reference_t<T>&&>(t);
}
它做的事情只是把一个表达式变成右值(xvalue),让重载决议有机会选中移动构造函数或移动赋值运算符。真正"搬东西"的是那些函数。
所以 const 对象 move 了也白 move
const std::string s = "hello";
std::string t = std::move(s); // 调用的是拷贝构造
std::move(s) 的类型是 const std::string&&,移动构造函数接受的是 std::string&&,绑定不上,于是退回到 const std::string& 的拷贝构造。编译器不会报错,clang-tidy 的 performance-move-const-arg 能查出来。
return 时不要写 std::move
std::vector<int> make() {
std::vector<int> v(1000);
return std::move(v); // 不好
}
直接 return v; 时,编译器可以做 NRVO,连移动都省掉。加了 std::move,返回表达式不再是一个简单的局部变量名,NRVO 就失效了,只能退化成一次移动。GCC 的 -Wpessimizing-move 会对这种写法给出警告。
即使 NRVO 没发生,标准也规定返回局部变量时会先尝试把它当右值处理,所以本来就会移动,手写 std::move 没有任何好处。
被移动后的对象
标准库类型被移动后处于"有效但未指定"的状态:可以析构、可以重新赋值,但不应该假设它的内容。比如被移动后的 std::string 在大多数实现里是空的,但这不是标准保证的。
std::string a = "data";
std::string b = std::move(a);
a.clear(); // 先恢复到已知状态再用
a = "new"; // 重新赋值也可以