左值、右值
前言
本笔记并不是一篇系统性的规范性笔记,其目的仅仅是帮助许多和我一样,反复观看过多次C++11右值引用,但是看的懵懵懂懂,没有记忆点的人。
不明白右值引用为什么要出现,为了解决什么问题而出现。
这篇笔记只是我自认为明白了一些实质的东西写下来的,阶段性口语化的感悟。
如果对,可以帮助像我一样的人加深记忆点。
不对的还请各位指点。
系统性的笔记,大家可以配合链接中的两篇原文来看。
建议大家直接跳到总结看一下,再看前面的内容
C++98
这是C++98的规范,后面出现右值引用后变了
C++在C++98时便遵循C模型,引入了左值、右值的概念。
- 左值(lvalue) :表达式结束后依然存在的持久对象。
- 右值(rvalue) :表达式结束后就不再存在的临时对象。
之所以取名左值右值,是因为在等式左边的值往往是持久存在的左值类型,在等式右边的表达式值往往是临时对象。
a = 152;
a = ++b;
a = b+c*2;
a = func();
字面量(字符字面量除外)、临时的表达式值、临时的函数返还值这些短暂存在的值都是右值。更直观的理解是:有变量名的对象都是左值,没有变量名的都是右值。(因为有无变量名意味着这个对象是否在下一行代码时依然存在)
值得注意的是,字符字面量是唯一不可算入右值的字面量,因为它实际存储在静态内存区,是持久存在的。
透彻理解C++11 移动语义:右值、右值引用、std::move、std::forward - KillerAery - 博客园 (cnblogs.com)
C++11
右值引用的引入主要是为了实现移动语义和完美转发,而不是修改右值内容
std::move(左值); 使用了move就能把左值当作右值作为参数传入参数是右值的函数
自悟:
1.移动语义
为了解决深拷贝中的需要一个临时对象的问题,移动语义被创建,就是直接转换资源的归属权,无需中间对象
这里面经常提到的词语:
移动:就是参数用了右值引用
复制:构造函数的写法string(string s) 类似
赋值重载:使用=赋值的函数,因为往往=的函数还有一个参数是左值的赋值,所以现在这个移动赋值重载函数就是一个重载的函数
后面链接给的:因此,我们在调用insert和push_back时,如果涉及深拷贝的问题,尽量传右值(匿名对象),这样可以减少深拷贝的问题。
2.完美转发
完美转发是为了解决,右值引用传递过程中,从右值退化为左值的问题出现的
如同下面这个调用
目的是:传的是右值,编译器匹配右值引用的赋值重载函数,x赋值给*pos时会调用string类的移动赋值重载函数,进行资源转换
但是实际上:
但是在上面调用参数是右值引用的insert接口中存在一个问题,如果右值引用x去接受一个右值,那么这个x就会退化成一个左值,所以*pos=x;这个过程是调用的时候是会直接调用赋值重载函数,不是移动拷贝构造函数。因此,为了保持x是一个右值,那么我们可以做这样一个动作:
std::forward(x):它可以在传参的时候保持x原生属性,也就是可以保持x的右值属性,因此,这样可以保证*pos=x去调用移动赋值重载函数。
这里需要说明的是 T&&x 传入的确实是右值,但是x作为一个对象,其实是一个左值,只是存储的是一个右值
所在在执行*pos = x 的时候,因为=传入的是一个左值,所以肯定调用的是左值的赋值重载函数,而不是右值的
修改:
总结
其实右值引用就是为了解决深拷贝开销太大的问题
如果是普通的拷贝构造和赋值函数都会进行一次开销大的创建临时对象的深拷贝
现在加入右值引用,就是直接告诉编译器,不用那么麻烦,直接把资源的归属权转移就行
所以如果你传入的是左值,就是传统的深拷贝,传入的是右值,就是开销更小的一种资源转移方式,传入的参数的值类型决定了函数处理方式,影响开销。
上面就是移动语义为什么要出现的原因,完美转发则是保持右值原生属性的一种机制。
ChatGPT评价
你的理解基本上是正确的,移动语义的引入确实是为了解决深拷贝开销过大的问题,以及在一些情况下可以利用对象的资源直接转移而不进行不必要的复制。
当一个对象被传递给一个函数时,如果这个对象是一个右值,即将被销毁的临时对象或者可以安全转移资源的对象,移动语义允许将其资源直接转移到函数内部,避免了不必要的复制操作,提高了效率。而如果对象是一个左值,传统的拷贝构造函数会被调用进行深拷贝。
完美转发确保了传递给函数的参数的值类别被保留,这意味着无论传递的是左值还是右值,都可以正确地将参数转发给其他函数,保持了参数的原始性质。
因此,你的理解在描述移动语义和完美转发的概念上是正确的。这些机制的引入确实是为了优化资源的管理和传递,并降低程序的开销。
特殊的
const 对象即可被认为是左值,也可被认为是右值,取决于具体情况
在 C++ 中,const 对象既可以是左值也可以是右值,具体取决于使用它的上下文。让我们来详细解释一下:
- 左值:当 const 对象被声明并且在表达式中作为一个标识符时,它通常被视为左值。这意味着你可以获取它的地址,并且可以修改它所指向的值。
cppCopy codeconst int x = 10;
int* ptr = &x; // 合法,获取 const 对象的地址
- 右值:当 const 对象出现在需要右值的上下文中,它会被视为右值。例如,如果它被传递给接受右值引用的函数,或者作为返回值,则它被认为是右值。
cppCopy codevoid func(int&& rvalue_ref);
func(5); // 合法,5 是一个右值
func(x); // 合法,const 对象 x 在这里作为右值被传递给函数
总之,const 对象的左值或右值属性取决于它在表达式中的上下文。
【c++复习笔记】——右值引用(概念,使用场景),移动拷贝构造函数,赋值拷贝构造函数。_为什么移动函数直接复制堆区的数据-CSDN博客