std::move是 C++11 引入的移动语义核心工具
1.功能
std::move不移动任何东西,它是一个类型转换工具,将左值(有名字的变量)无条件转换为右值引用(T&&),从而“骗过”编译器,去调用移动构造函数或移动赋值运算符。
目的:转移资源所有权(如堆内存、文件句柄),避免深拷贝的开销。
效果:被移动的对象进入“有效但未指定”的状态(通常是空),不应再访问其内容(除非重新赋值)。
2. 基础用法
2.1 移动构造函数(转移堆内存)
class Buffer { int* ptr; public: // 移动构造函数(noexcept 是关键优化提示) Buffer(Buffer&& other) noexcept : ptr(other.ptr) { other.ptr = nullptr; // 防止析构时 double free } }; Buffer a; Buffer b = std::move(a); // 触发移动,a 变为空指针noexcept是 C++11 引入的异常规范修饰符,核心作用:标记函数不抛异常和编译器优化提示。
2.2移动赋值运算符(转移并清理旧资源)
Buffer& operator=(Buffer&& other) noexcept { if (this != &other) { delete ptr; // 释放自己的旧资源 ptr = other.ptr; // 接管别人资源 other.ptr = nullptr; } return *this; } b = std::move(a); // 触发移动赋值2.3 STL 容器的高效操作(最实用)
std::vector<std::string> v1 = {"a", "b"}; std::vector<std::string> v2 = std::move(v1); // O(1) 转移指针,不拷贝元素 // 向容器插入大对象,避免拷贝 std::vector<MyClass> vec; MyClass obj; vec.push_back(std::move(obj)); // 直接转移,不复制3. 注意事项
1:
std::move后,若要用原变量,必须重新赋值后才安全。移动后访问是未定义行为(通常崩溃)。std::string s = "hello"; std::string t = std::move(s); std::cout << s; // 未定义行为!s 可能为空或乱码 s = "new"; // 重新赋值后可安全使用2:只有定义了移动构造函数的类型才高效移动(如
std::vector)。简单类型(int)拷贝和移动无区别。3:
const对象用std::moveconst std::vector<int> v;即使
std::move(v),因为无法修改,会退化为拷贝(const 右值无法绑定到非 const 移动构造)。
4. 与std::forward的区别
| 工具 | 作用 | 使用场景 |
|---|---|---|
std::move | 无条件转为右值 | 明确要转移资源时 |
std::forward | 有条件转为右值(保留原始类型) | 模板完美转发(转发参数给下一层函数) |
template<typename T> void wrapper(T&& arg) { // 保持 arg 的原始左右值属性 other_func(std::forward<T>(arg)); }5.T&&
T&&在 C++ 中根据上下文有两种完全不同的含义,分别是右值引用和万能引用(转发引用)。
判断标准:看T是否被推导。
5.1. 右值引用(Rvalue Reference)
当T是具体类型时,T&&就是右值引用。
作用:绑定到即将销毁的临时对象,用于实现“移动语义”,窃取资源。
特征:只能接收右值(临时量、
std::move的结果)。
void func(std::vector<int>&& vec) { // T 是具体类型 vector<int> // 只能接收右值 } std::vector<int> v; func(std::move(v)); // 可以 func(v); // 编译报错,左值无法绑定到右值引用5.2. 万能引用(Forwarding Reference)
当T是模板参数(且T由实参推导)时,T&&是万能引用。
作用:在模板中保持值类别不变(左值保持左值,右值保持右值),常用于
std::forward完美转发。特征:既能接收左值,也能接收右值。
template<typename T> void func(T&& arg) { // T 由实参推导,这里是万能引用 // 传左值 -> T 推导为 int& // 传右值 -> T 推导为 int } int x = 10; func(x); // 左值可以 func(20); // 右值也可以func(x):x是左值int→T推导为int&→arg类型是int&(左值引用)func(T&& arg) → func(int& && arg) (引用折叠)→ func(int& arg)
func(20):20是右值int→T推导为int→arg类型是int&&(右值引用)func(T&& arg) → func(int&& arg)
引用折叠规则
| 原始类型 | 折叠后 |
|---|---|
T& & | T& |
T& && | T& |
T&& & | T& |
T&& && | T&& |
5.3. 特殊情况:auto&&
auto&&也是万能引用,同样根据初始化表达式推导:
auto&& r1 = 10; // 右值 -> int&& int x = 5; auto&& r2 = x; // 左值 -> int&4. const T&&
注意:const T&&永远是右值引用,不是万能引用,且极少使用(因为移动时通常不需要 const)。