ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

C++11--右值引用和移动语义

2026/9/14 20:05:14 拓冰建站 浏览量
C++11--右值引用和移动语义 1左值右值定义1左值左值是一个表示数据的表达式如变量名或解引用的指针一般具有持久状态存储在内存中左值可以出现在赋值符号左边/右边可以获取一个左值的地址。下述的“p”“*p”“b”“c”“s”“s[0]”都是左值。int* pnew int(1); *p10; int b1; const int cb; string s(11111); s[0]x;2右值右值也是一个表达式要么是字面值常量要么是表达式求值过程中创建的临时对象右值只能出现在赋值符合右边且不能取地址。double x1.2,y1.3; //该xy是左值 19; x y; fmin(x,y); string(1231); //以上都是常见的右值而能否取地址的规定是语言库中明文规定“”要求左值。那么为什么“右值”不能取地址从上述右值的示例来看右值的用法就是短暂的可见其生命周期必然是短暂的。如字面量“19”对于编译器而言它不是一个对象而是编译后直接编码在机器指令里当立即数或者放只读数据段。那么也不存在地址。如表达式结果“xy”cpu计算完之后会将结果放入寄存器而寄存器没有内存地址寄存器是cpu内部暂存的单元。如临时对象“string(12333)”之所以叫临时对象就是会在完整表达式结束那一刻销毁那么此时取地址该指针会瞬间悬空。我猜这也是不允许单独取地址的原因之一3左值和右值的区别由上述可见左值和右值的主要区别在于“能否取地址”。另外说的是其英文翻译左值lvalue---locate value,意为存储在内存中有明确存储地址的对象。右值rvalue---read value 则意为提供数据值只读但不能寻址的对象。2左值引用和右值引用无论左值/右值引用都是给对象起别名不开空间。左值引用就是给左值起别名右值引用就是给右值起别名。Type n1x; //左值引用 Type nn1y; //右值引用 int nn210;左值引用不能直接引用右值但const左值引用可以引用右值右值引用不能直接引用左值但右值引用可以直接引用move左值move是库中一个函数模板其内部本质是进行强制类型转换且涉及一些引用折叠后续再谈。//左值 int* pnew int(0); int b1; int d9; const int cb; *p10; string s(111111); s[0]x; int lbb; //左值引用给左值取别名 int* lpp; int lpp*p; string lss; char lcs[0]; //右值引用给右值取别名 int r110; int r2bd; //需要注意此时的r1,r2,r3属性是左值 int r3fmin(b,d); string r4string(111111); //const左值引用可以给右值取别名 const int lr110; const string lr2string(1111); const int lr3bd; //右值引用引用move int rl1move(b); int* rl2move(p); //也就是说 move(b) 是左值 int rl3move(*p); string rl4move(s);由上述提到 的变量表达式都是左值属性也就意味着一个右值被右值引用绑定后右值引用变量表达式是左值。int r110; int rr1move(r1);3引用延长生命周期右值引用和const左值引用可以延长临时对象的生命周期但const左值引用的这些对象不能被修改。string s111111; //s1是左值 const string s2s1s1; //s1s1是右值这是const左值引用引用右值 string s3s1s1; //右值引用右值 s3s1; //“s1s1”的生命周期延长 //s2s1; //错误const左值不可修改需维持const特性4左值和右值参数匹配在C98中对于一个参数为const左值引用的函数可以传左值和右值在C11中若方便重载各个类型左值引用const左值引用右值引用则会根据传递的参数进行配对。其中右值引用若没有被重载则右值会调用const左值引用。void fun(int x) { cout左值引用endl; } void fun(const int x) { coutconst左值引用endl; } void fun(int x) { cout右值引用endl; } int main() { int i1; //左值 const int b2; //const 左值 int x2; //右值引用 fun(i); //fun(int) fun(b); //fun(const int) fun(1); //fun(int) 若没有则 fun(const int) fun(move(i)); fun(x); //fun(int) fun(move(x)); return 0; }5左值引用使用场景左值引用就是我们日常所说的引用那么引用的主要使用场景在于函数的传参和返回值。引用的一个概念就是“对象别名”所以传参时传引用那么就相当于传别名相比传值传参减少一次拷贝且能修改实参对象。传引用返回值时也是减少一次拷贝。上述的传引用返回的对象生命周期都比函数的生命周期要长左值引用已经解决了大部分的拷贝问题但还有一些场景无法传引用返回返回的局部对象在函数结束时销毁只能传值再拷贝返回。右值引用也没有解决这部分的问题6移动构造和移动赋值移动构造函数是一种构造函数类似拷贝构造函数要求第一个参数是该类类型的右值引用。移动赋值是一个赋值运算符的重载与拷贝赋值构成函数重载也要求第一个参数是该类类型的右值引用。那么移动构造和移动赋值是什么形式class string { public: void _swap(string s) { swap(_str,s._str); swap(_size,s._size); swap(_capacity,s._capacity); } //移动构造 string(string s) { _swap(s); } //移动赋值 string operator(string s) { _swap(s); return *this; } private: char* _strnullptr; size_t _size0; size_t capacity0; };如上述移动构造和移动拷贝就是一种“资源掠夺”利用右值引用的特性延长其生命周期再掠夺其资源。右值引用变量是左值而只有对于类似string/vector这样包含深拷贝的类移动构造和移动赋值才有意义才能提高效率因为这直接交换swap的效率高于拷贝资源的效率。浅拷贝只复制成员变量的值比如成员变量是指针那么只会复制该指针地址而不管指针指向哪里这样就会导致两个对象指向同一份资源导致后续的错乱。深拷贝对于指针变量会重新申请一块空间把指针指向的资源也拷贝一份两个对象分别管理不同的一样的资源。因此包含深拷贝的类移动构造和移动赋值才有较大意义。7右值类型分类C11之后对右值类型进一步进行了分类右值分为纯右值pure value—prvalue和将亡值expiring value —xvalue。纯右值通常指的是字面常量求值结果无名临时对象。将亡值指返回右值引用的函数的调用表达式和转换为右值引用的转换函数的调用表达。如move(x)通常像生命周期延长的右值且有名字。泛左值泛左值包含将亡值和左值。8引用折叠C中不能直接定义引用的引用意义参照指针的指针但可以通过模板或typedef的类型操作就可以构成“引用的引用”也就是引用折叠。int r1i; //错误使用引用折叠时C11也给定了规则右值引用的右值引用折叠成右值引用其他所有组合都折叠成左值引用。不管是typedef还是类都用同一套规则达到同一种“引用折叠”的形式。同时也需要符合前述左右值引用的规则。//typedef ——引用折叠 typedef int lref; typedef int rref; int n0; //根据规定折叠之后相当于 lref r1n; //r1是int lref r2n; //r2是int rref r3n; //r3是int rref r41; //r4是int --根据规则 //类 ---引用折叠 templateclass T //引用折叠规则限定fun1实例化后总是一个左值引用 void fun1(T x) {} templateclass T //fun2可以是左值引用也可以是右值引用 void fun2(T x) {} fun1int(0); //报错fun1int相当于fun1(int x)传右值0则会报错以下类似。 fun1int(0); //x fun1int(0); //x fun1const int(0); //正确(const int)可以传右值 fun2int(0); //推导成int) fun2int(n); //int) fun2int(0); //(int)9完美转发完美转发是一个函数模板forward本质通过引用折叠的方式实现需要保持对象属性的一些场景。在日常使用场景中如下面的模板函数再调用另外一个函数就可能会出现“不可控”的引用折叠就会导致其无法达到想要效果所以此时需要“完美转发forword”来保证对象t的属性。templateclass T T forward(remove_referenceT arg) noexcept { return static_castT(arg); } void fun(int x){ .. } / /..左值引用 void fun(const int x){ .. } / /..const左值引用 void fun(int x){ .. } //..右值引用 void fun(const int x){ .. } / /..const右值引用 templateclass T void function(T t) { fun(t); //fun(forwardT(t)); //作用位置 } int main() { function(0); //传入右值推导出T为int最终类型为int int a1; function(a); //传入左值T为int引用折叠最终为int function(move(a)); const int b9; function(b); //传入左值T为const int引用折叠最终为const int function(move(b)); return 0; }