彻底理解c++中的五种值,临时对象,移动语意,引用指针,const等的概念和关联
c++的引用和指针
引用:
引用,是变量或对象的别名,这么解释其实还是有点抽象的;
如果是指针,则好理解,是对象的虚拟内存地址;所以在赋值拷贝等容易有实体理解;而引用,在这些常见下,又是什么样的实际操作?
考虑如下例子:
1 | 为了理解引用是什么,来看这个例子: |
所以对于汇编来讲,引用和指针本质上是一样的,都是存放变量的地址,只是编译器对引用做了封装和限制,不允许像操作指针那样操作引用,而实际上
修改引用的值,是修改对应位置的值,所以会影响源对象,而将引用赋值给另一个变量,是将其值赋值过去,而不是将地址赋值过去;
c++的左值引用和右值引用
左值引用,和指针没啥太大本质差别,给程序员的权限比指针少了些,但是也是很多的;而右值引用,是编译器对临时对象的权限的释放,即允许程序员能有一定的控制临时对象的权利;
所以先来看临时对象;可能一切都是由临时对象惹得锅;
c++临时对象
我们知道,如果是c语言,是很少有临时对象这些说法的,因为c没有类和对象的概念,即使是struct,在赋值,构造的时候,也不需要调用构造函数,一般是映射过去;
而c++不一样,c++有了类和对象,和基础类型不同的是,对赋值传值等场景下,需要调用拷贝函数等,而且拷贝函数的参数等不能像基础变量那样,直接传递值到寄存器,往往涉及到
要用内存作为跳板,比如c=a+b,当它们是基础类型如int时,a+b的值甚至可以存在寄存器中,再赋值到c对应的内存中;而如果是对象,那a+b时,要调用operator+ 函数,结果
要构造一个临时对象来赋值给c,这个时候要调用赋值函数;
而临时对象的出现,伴随着即时的构造和销毁,会带来相应的开支,效率降低;
在一些场景下,对临时对象的某个操作可以提高效率,减少开支;如在赋值时,不是去做将临时对象拷贝到新对象上,而是将临时对象的地址赋值到新对象上,称为移动;
这样会少一次拷贝,提升效率;此为移动语意;
伴随着这种提升的出现;面临着一个问题,就是哪些可以用来移动,哪些只是临时对象,完全由编译器去控制? 编译器可能不够清楚,需要程序员去提示它需要移动,不能立刻销毁;
要延长寿命; 传统的左值右值无法精确区分,所以为了用户明确表示它要移动,编译器制定了规则,用来区分,于是就有了下面的五种值;
c++的五种值:lvalue,xvalue,rvalue,gvalue,prvalue
expression
/ \
glvalue rvalue
/ \ / \
lvalue xvalue prvalue
所以实际上是三种值,只是又将三种值分为两类,为了简单,直接看这三种值:
主值类别对应于表达式的两个属性:
具有标识 :可以通过比较对象的地址或它们标识的功能(直接或间接获得)来确定该表达式是否与另一个表达式引用相同的实体;
可以从以下位置移动:移动构造函数,移动赋值运算符,或实现移动语义的另一个函数重载可以绑定到表达式。
表示为:
具有身份并且不能被移走的被称为左值表达式 ; lvalue,即编译器不会自动帮你销毁它,除非到达了作用域结尾;
具有身份并可以移出的称为xvalue 表达式 ; xvalue,编译器以此知道它不能立刻被销毁,直到你移动了它;所以它叫expiring值,你移动了它,它就亡了
没有身份且可以移出的被称为prvalue 表达式 ; prvalue,完全由编译器去控制,用户几乎无感知它的生命周期;
没有身份并且无法从中移走的人不会被使用。
c++右值引用和五种值的关系
右值引用,可以说是对应了xvalue了,左值引用是lvalue,因为修改它,就像修改左值一样;而临时对象prvalue,没有引用之说,你不能引用它,除非你用它构造一个右值引用或左值引用;
它就是一个单纯的值,只在内存存在很短的时间;
c++临时对象,右值引用,和五种值的关系
到这里,我们可以知道临时对象,是五种值来源的关键,而右值引用,是对临时对象带来的问题的解救;
const &和& 的区别和使用
看下面这个例子,你会发现对汇编来讲,其实是一样的,但是编译器会检查你的行为,直接在编译阶段过滤掉你尝试对const的改动;
1 | | 28 movl $3, -28(%rbp) | 19 int main() |
move移动语意
看下面的例子就知道了,move只是对封装了下,和const &的引用类似;
1 | 49 movl $3, -20(%rbp) | 9 int main() |
一些例子和基础使用:
- 左值引用可以被修改: 很明显就像指针;
- 右值引用不能被修改? 为什么?
我们来看一个例子:
1 |
|
- 其他的理解:
- 对vector的emplace_back,有如下几种情况:
(1) emplace_back(A()) //将A的内存地址直接move;A()会被清理
(2) emplace_back(x) //x是基础变量,则x的地址被拷贝;
(3) emplace_back(3) //和push_back(int{3})没有区别,都需要再开辟一个内存地址,注意参数的不同
所以emplace_back要比push_back快一倍几乎;
- 各种例子:其他例子:ref:
1
2
3
4
5
6
7
8
9
10
11struct X { int n; };
extern X x;
4; // prvalue: does not have an identity
x; // lvalue
x.n; // lvalue
std::move(x); // xvalue
std::forward<X&>(x); // lvalue
X{4}; // prvalue: does not have an identity
X{4}.n; // xvalue: does have an identity and denotes resources
// that can be reused
https://www.geeksforgeeks.org/understanding-lvalues-prvalues-and-xvalues-in-ccwith-examples/
https://en.cppreference.com/w/cpp/language/value_category