当你开始更频繁地使用移动语义时,很快就会发现:有时你想触发移动语义,但手头的对象却是左值而非右值。下面这个简单的交换函数恰好说明了这一点:
#include <iostream>
#include <string>
template <typename T>
void mySwapCopy(T& a, T& b)
{
    T tmp{ a }; // 调用拷贝构造函数
    a = b;      // 调用拷贝赋值运算符
    b = tmp;    // 再次调用拷贝赋值运算符
}
int main()
{
    std::string x{ "abc" };
    std::string y{ "de" };
    std::cout << "x: " << x << '\n';
    std::cout << "y: " << y << '\n';
    mySwapCopy(x, y);
    std::cout << "x: " << x << '\n';
    std::cout << "y: " << y << '\n';
    return 0;
}
mySwapCopy 接收两个 T 类型的对象(此处为 std::string),通过三次拷贝完成交换。程序输出:
x: abc
y: de
x: de
y: abc
显然,频繁拷贝效率低下;三次深拷贝带来了大量不必要的字符串创建与销毁。
若改用移动语义,仅需三次移动即可完成同样的交换,性能会大幅提升。然而,a 与 b 都是左值引用,默认只能触发拷贝。如何强制使用移动语义?答案就是 std::move。
std::move
在 C++11 中,标准库提供了 std::move,其功能是用 static_cast 将实参强制转换为右值引用,从而启用移动语义。
只需包含头文件 <utility> 即可使用。
将上例改写为 mySwapMove:
#include <iostream>
#include <string>
#include <utility> // for std::move
template <typename T>
void mySwapMove(T& a, T& b)
{
    T tmp{ std::move(a) }; // 调用移动构造函数
    a = std::move(b);      // 调用移动赋值运算符
    b = std::move(tmp);    // 再次调用移动赋值运算符
}
int main()
{
    std::string x{ "abc" };
    std::string y{ "de" };
    std::cout << "x: " << x << '\n';
    std::cout << "y: " << y << '\n';
    mySwapMove(x, y);
    std::cout << "x: " << x << '\n';
    std::cout << "y: " << y << '\n';
    return 0;
}
输出与之前相同:
x: abc
y: de
x: de
y: abc
但性能显著提升:
- tmp初始化时,- std::move(a)把- x转为右值,触发移动构造;
- 后续两次 std::move触发移动赋值,资源被移动而非复制。
另一示例:向容器插入元素
我们也可以用 std::move 把左值元素移动到容器中,避免拷贝。
#include <iostream>
#include <string>
#include <utility>
#include <vector>
int main()
{
    std::vector<std::string> v;
    std::string str{ "Knock" };
    std::cout << "Copying str\n";
    v.push_back(str);          // 传入左值 -> 拷贝
    std::cout << "str: " << str << '\n';
    std::cout << "vector: " << v[0] << '\n';
    std::cout << "\nMoving str\n";
    v.push_back(std::move(str)); // 传入右值 -> 移动
    std::cout << "str: " << str << '\n'; // 值已转移,结果不确定
    std::cout << "vector: " << v[0] << ' ' << v[1] << '\n';
    return 0;
}
作者机器上输出:
Copying str
str: Knock
vector: Knock
Moving str
str:
vector: Knock Knock
- 第一次 push_back传入左值str,执行拷贝,str保持不变。
- 第二次传入 std::move(str),触发移动,向量元素“窃取”str内部资源。
- 注意:移动后 str处于有效但不确定的状态。
被移动对象的合法状态
当资源从临时对象移出时,其后续状态无关紧要,因为临时对象会立即被销毁。
但对于通过 std::move 转出的左值对象,我们仍需知晓其状态:
- 学派一:主张将对象重置为默认/空状态,使其不再占有资源。
- 学派二:主张仅做最便捷的处理,不强制清空,除非方便。
C++ 标准规定:
“除非另行说明,标准库中被移动后的对象应处于有效但未指明的状态。”
上例中,str 被打印为空串,但这并非强制要求;任何合法字符串(含空串、原串或其他)都可能出现。因此,切勿依赖被移动对象的值;若需继续使用,应先赋予新值。
在 mySwapMove() 中,我们先移出 a 的资源,再移入新资源,期间并未读取 a 的旧值,这是安全的。
何时可安全复用被移动对象?
- 可调用不依赖当前值的函数:如赋值、清除、重置、判空等。
- 避免调用依赖当前值的函数:如 operator[]、front()等,因为容器可能已空。
核心洞见
std::move() 向编译器表明:程序员不再需要该对象的当前值。
仅在确实不再需要某左值时使用 std::move,并在此后不作任何关于其值的假设。
若仍需使用,应先为其赋予新值。
其他应用场景
- 排序算法 
 选择排序、冒泡排序等需要频繁交换元素。过去只能拷贝,如今可用- std::move提升效率。
- 智能指针转移所有权 
 可将一个智能指针所管理的资源转移给另一指针。
相关补充
- std::move_if_noexcept():若对象的移动构造函数声明为- noexcept,则返回可移动的右值;否则返回可拷贝的左值。
 详见 课程《std::move_if_noexcept》。
结论
每当我们希望把左值当作右值对待,以触发移动语义而非拷贝语义时,即可使用 std::move。
