左值引用&右值引用
我一度认为是否会用左值/右值引用能够判断出一个人是否了解modern C++
左值:理解为等号左边的值(能取地址)
右值:理解为等号右边的值,即表达式结束后不再存在的临时对象(不能取地址),比如常量表达式,函数返回值,和存在寄存器中的临时值等
纯右值:纯粹的字面量,比如非引用的返回值、表达式(包括lambda)等
将亡值:可以被移动的临时值(第一次看到可能比较难理解,不妨先往后看)
引用:一个变量的别名(没去了解过底层原理,感觉像是指针的某种高级封装)
对引用的操作就相当于对地址中值的操作,也就是给引用变量赋值的前提是,被引用的变量是可以取址的(比如存在寄存器中的变量)。举个例子
int num = 10;
int num1 = num+1;
int* pnum = #
此时,如果想对上面的一些变量进行引用,则至少需要知道它们在主存中的地址,也就是是一个左值
int& a = num;
int& a = 1;
int*& p = pnum;
int*& p = #
后两个是非常好的例子,pnum是存储在内存中的变量,其值为num的地址,所以可以被引用;&num是临时变量(右值),不在主存中被分配空间,所以不能被引用
也就是说,pnum和&num的值都是num变量的地址,但是它们存在的方式取决了其能否被引用
如果一定要引用临时变量,就需要先将其拷贝到内存中(就像
i
n
t
?
p
=
&
n
u
m
int* p =\&num
int?p=&num),然后再去引用内存中的变量,从而间接引用之前的临时变量
以上提到的引用均为左值引用(T&),也是通常提到“引用”时所指的引用
这里不得不提到常量左值引用(const T&),常量左值引用没有左值引用的限制,可以接受右值初始化,但是限定了只读
但是因为右值是用临时变量存储的,赋给左值的时候是做了深拷贝的,然后再销毁临时变量。深拷贝必然带来资源的消耗,所以就需要一种能够直接引用右值的方法
右值引用则是对&num这种右值的引用,不能用左值初始化,一定要用的话需要std::move将左值参数转换为右值
简单来说,右值引用就是延长右值的声明周期,不再做深拷贝,仅仅去引用寄存器中的临时变量。从这个角度来看,右值引用的速度是比左值引用快很多的
移动和拷贝
拷贝:通过一个对象,生成与其相同的另一个对象
移动:把一个对象移动到另一个位置
传统C++在移动时,必须使用先复制再析构的方式,非常反人类。一种更理想的移动方式,就是将旧对象的“使用权限”交给新对象,这就可以通过一个移动构造函数来实现
A(A&& a): p(a.p){
a.p = nullptr;
}
简单来说,就是通过右值引用拿到旧对象的使用权限,然后解除旧对象自身的引用
也就是说如果打算把一个对象赋值给另一个对象,但是这个对象之后不再打算使用了,就可以用移动的方式减少资源消耗,举个例子
vector<string> v;
string str;
v.push_back(move(str));
完美转发
引用坍塌规则:对引用进行引用时,函数形参
T
&
&
T\&\&
T&&不一定能进行右值引用,当传入的是左值时,会被推导为左值。也就是说只有传入右值引用才是真的右值引用
参数转发存在一个问题,引用类型本身是一个左值,所以当传递一个右值引用参数的时候,会被当做左值处理,举个例子
template<typename T>
void pass(T&& v){
reference(v);
}
void reference(int& v);
void reference(int&& v);
pass(1);
int l = 1;
pass(l);
无论是
p
a
s
s
(
1
)
pass(1)
pass(1)还是
p
a
s
s
(
l
)
pass(l)
pass(l),虽然传入pass的一个是左值,一个是右值,但是由于v本身是一个引用,意味着v是一个左值,所以只会调用左值引用的reference
forward函数可以解决引用坍塌的问题,在传递参数时会保持其原有类型
move只是把左值转化为右值,forward只是做类型转换
|