C++11特性——右值引用与移动语义
右值引用与移动语义
右值引用 &&
左值(Lvalue):有名字,可取地址,生命周期可持续。
右值(Rvalue):临时对象,不可取地址,生命周期短,例如字面量、返回值等。
int a = 10; // a 是左值 int b = a + 1; // a + 1 是右值(临时值) |
为什么要有右值引用?
只有左值引用(T&),没有右值引用(T&&)。导致一个问题:无法区分一个值是“即将销毁的临时变量”还是“正在使用的对象”。
右值引用的存在,是为了优化性能,避免不必要的拷贝。
std::move 与 std::forward
std::move 并不真正进行“移动”,它只是一个类型转换工具,把一个变量强制转换为右值引用,从而允许“移动语义”生效。
std::move 的作用是告诉编译器:“我不再使用这个变量了,你可以把资源搬走。”
搭配右值引用,触发移动构造/移动赋值,避免不必要的拷贝。
示例:
#include<iostream> #include<string> class Myclass { public: std::string data; Myclass(std::string& other):data(other) { std::cout<<"拷贝!"<<std::endl; }; Myclass(std::string&& other):data(std::move(other)) { std::cout<<"移动赋值!"<<std::endl; }; }; int main() { std::string str{"Hello"}; Myclass a(str); Myclass b = std::move(str); std::cout << std::endl; return 0; } |
std::forward:
std::forward 会根据模板参数 T 的类型,把参数转回它原来的值类别。
原理;本质就是一个 条件版的 static_cast:
template<typename T> T&& forward(typename remove_reference<T>::type& t) noexcept { return static_cast<T&&>(t); } |
如果 T 是 int&(表示原来传的是左值),T&& 折叠成 int& → 返回左值。
如果 T 是 int(表示原来传的是右值),T&& 还是 int&& → 返回右值。
特性 | std::move | std::forward |
作用 | 无条件把值变右值 | 条件保留原值类别 |
使用场景 | 想强制触发移动语义 | 模板中做完美转发 |
是否会改变左值 | 会变右值 | 不会 |
常见位置 | 普通函数内部 | 模板函数内部(配合 T&&) |
注:std::move = “全部改成右值”、 std::forward = “保留原来的左/右值身份”
移动构造函数、移动赋值运算符
传统的拷贝构造函数和拷贝赋值运算符通过深拷贝(复制数据)实现对象复制,效率较低,尤其是涉及动态资源(如堆内存、文件句柄等)时。移动构造函数和移动赋值运算符的引入,允许“窃取”临时对象的资源,避免不必要的拷贝,提高程序性能。移动赋值 就是直接把旧房子的门钥匙交给你(直接接管资源),完全不用搬东西。
移动构造函数:
语法:ClassName(ClassName&& other) noexcept;
参数类型是右值引用(ClassName&&)。
noexcept 表示此函数不会抛异常
功能:
将 other 对象内部的资源(如指针)直接转移给新对象。
将 other 的资源指针置空或重置,避免双重释放。
示例:
class MyMC { public: std::string data; MyMC(const std::string& str):data(str){}; ~MyMC(){}; //移动构造函数 MyMC(MyMC&& other):data(other.data){}; }; |
移动赋值运算符:同样接收右值引用参数
功能:先释放当前对象的资源(防止内存泄漏)、将 other 的资源指针接管、将 other 的资源指针置空。
语法:ClassName& operator=(ClassName&& other) noexcept;
MyMC& operator=(MyMC&& other) noexcept //重载移动赋值运算符 { return *this; } |
完美转发(perfect forwarding)
完美转发 是指在模板函数中,将接收到的参数 原封不动(保持它的左值/右值属性) 转发给另一个函数。如果你传进来的是左值,它会被当成左值转发;如果你传进来的是右值,它会被当成右值转发。
为什么需要完美转发?
模板参数 T 按值接收时,会把右值变成左值
转发引用:
template<typename T>
void func(T&& arg) { ... }
如果这种 T&& 出现在模板参数里,并且 T 要从函数参数类型推导出来,它不是普通右值引用,而是 转发引用
如果传进来是左值 → T 推导成 T& → 参数类型变成 T& && → 折叠成 T&(左值引用)。
如果传进来是右值 → T 推导成 T → 参数类型是 T&&(右值引用)。
具体规则;
T& & → T&
T& && → T&
T&& & → T&
T&& && → T&&
示例:
#include<iostream> #include<string> void process(int& x) { std::cout<<"左值版本\n"; } void process(int&& x) { std::cout<<"右值版本\n"; } template<typename T> void wrapper(T&& arg) { process(std::forward(arg)); } int main() { int a = 5; wrapper(a); // 左值版本 wrapper(10); // 右值版本 return 0; } |