左值,右值
左值(lvalue,即 locator value),是有名字的可以寻址的变量。
右值(rvalue,即 read value),是表达式求值过程中创建的无名临时对象。可以分为纯右值(prvalue,即 pure rvalue,包括临时对象,非引用类型的表达式或非引用类型的表达式)和将亡值(xvalue,即 expiring value,包括非引用类型的非静态数据成员和返回类型是对象的右值引用的函数调用)。
int a=3;// a 是左值,3 是右值
int b=a+1;// b 是左值,a+1 是右值
const int c=a;// c 是常左值,a 是左值但是这里被当成了右值
实际上,将亡值属于广义左值(glvalue,即 generalized lvalue),因为将亡值有标识符但不可以寻址。除了将亡值的右值被称为纯右值(prvalue,即 pure rvalue)。
expression
/ \
/ \
glvalue rvalue
/ \ / \
/ \ / \
lvalue xvalue prvalue
左值引用(lvalue reference):即 c++11 之前的引用,保存且只能保存一个左值的引用。
右值引用(rvalue reference):保存且只能保存一个右值的引用。
常左值引用(const reference):即既可以保存左值(包括常左值)也可以保存右值的引用(这是左值引用做不到的),不过是不能修改的。简称为常引用。
常右值引用:即仅保存右值的引用,不过是不能修改的。这个工作完全可以交给常引用来完成。右值引用主要用于移动语义和完美转发,这两个都要有修改右值的权限,所以没有用。
int &aa=a;// aa 是左值引用
const int &a1=a+1;// a1 是常引用
int &bb=a+1;b++;// bb 是右值引用,可修改
const int &&b2=a+1;// b2 是常右值引用,不会 CE 但没用
生命周期延长:正常情况下,一个变量的生命周期在超出作用域时结束,按生成顺序的逆序被销毁。但一个纯右值被绑定到一个引用上,它的生命周期则会延长到跟这个引用变量一样长,这样可以防止悬垂引动(返回临时变量的引用)。
string calc(){return string("abc");}
signed main(){const string &ref=calc();}// 生命周期延长
万能引用(universal reference):顾名思义,就是初始化时根据值类型来自动确定引用的类型。如果 T&&
中 T
是被推导出来的(包括 auto
和 template
),那这个 T
就是万能引用。万能引用可以绑在左值引用也可以右值引用还可以绑在常引用。
int f1(int&&t);// 非万能引用
int&&f2=f1();// 非万能引用
auto&&f3=f1();// 万能引用
template<typename T>int f4(std::vector<T>&&t);// 非万能引用
template<typename T>int f5(T&&t);// 万能引用
引用折叠:c++ 中引用的引用是被绑定值的引用而不是被绑定引用的引用。而新的引用的类型遵循引用折叠。只有把右值引用折叠到右值引用,得到的结果才是右值引用,否则均为左值引用。
using lref=int&;
using rref=int&&;
int n=0;
lref &r1=n;// r1 的类型是 int&
lref&&r2=n;// r2 的类型是 int&
rref &r3=n;// r3 的类型是 int&
rref&&r4=1;// r4 的类型是 int&&
move
函数本身并没有进行移动,它的效果是将一段左值强行转化为右值,这有什么用呢?假如你要把一个 vector
a
赋值给另一个 vector
b
,并且后面不再用到 a
,就不需要把 a
中的每个值复制一遍,只要把 a
的指针“偷”出来给 b
,然后把 a
标记为空就行了。这个时候就可以写一个拷贝构造函数传入一个右值,传入右值就可以把 b
强转右值后传入。本质上就是 static_cast<typename std::remove_reference<T>::type&&>(t)
(去除原先的引用之后加上右值引用)。顺带一提,move
的返回值是经典的将亡值。
t=std::move(b1);
b1=std::move(b2);
b2=std::move(t);// 一种常数时间交换 vector 的办法
forward
的效果是实现完美转发。forward
必须和万能引用一起使用,否则没有意义。本质上是 static_cast<T&&>(t)
。(采用引用折叠,把明明是右值引用但因为传递过程中有了名字变成左值引用的改回右值引用)
struct dector:std::vector<int>{
template<typename...args>
dector(args&&...qwq)
:std::vector<int>(std::forward<args>(qwq)...){}
};// 完美转发实现参数包转发