C++ 中的每个表达式都会生成一个值,该值属于 (左值 Lvalue,右值 Rvalue, Xvalue) 类别之一。 C++ 语言及其工具和规则的许多方面都需要正确理解这些值类别以及对它们的引用。 这些方面包括获取值的地址、复制值、移动值、将值转发给另一函数。
引用内存位置的表达式称为“左值”表达式。 左值表示存储区域的“locator”值或“left”值,并暗示它可以出现在等号 ( = ) 的左侧。 左值通常是标识符。左值是表达式结束(不一定是赋值表达式)后依然存在的对象。定义时 const 修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。
左值 (location value) 可以取地址的,有名字的,非临时的就是左值。一个可以用来存储数据的变量,有实际的内存地址(变量名)。【一个表示数据的表达式(如变量名或解引用的指针),且可以获取他的地址(取地址),可以对它进行赋值;它可以在赋值符号的左边或者右边。】
右值(real value) 不能取地址的,没有名字的,临时的就是右值。“匿名”的“临时”变量,在表达式结束时生命周期终止。是一个可以生成临时对象的表达式或者是一个不可以被修改的值(字面值 literal (字符串常量除外))。【一个表示数据的表达式(如字面常量(string literals 除外)、函数的返回值、表达式的返回值),且不可以获取他的地址(取地址);它只能在赋值符号的右边。】
常见左值
常见右值:
C++ Primer 3rd 建议用前置操作符(++i,--i),只有在必要的时候才使用后置操作符(i++)。前置操作符返回的是左值(对象本身),而后置操作符返回的是右值。前置操作符需要的工作更少,只需加 1 后返回加 1 后的结果;而后置操作符则必须先保存原来的数值,以便返回未加 1 时的值,可能会花费更大的代价。
将亡值
将亡值是指 C++11新增的和右值引用相关的表达式,通常指将要被移动的对象、T&& 函数的返回值、std::move 函数的返回值、转换为 T&& 类型转换函数的返回值,将亡值可以理解为即将要销毁的值,通过“盗取”其它变量内存空间方式获取的值,在确保其它变量不再被使用或者即将被销毁时,可以避免内存空间的释放和分配,延长变量值的生命周期,常用来完成移动构造或者移动赋值的特殊任务。(更侧重于自定义类型的函数的返回值,表达式的返回值。)
引用:别的变量的别名,不占用额外内存空间。使用上要保证定义时被初始化。
左值引用只能给左值取别名。【左值引用只能引用左值】
左值:int num = 9;
左值引用:int& a = num;
右值引用只能给右值取别名。【右值应用只能引用右值】
右值:8; (x + y); function(x, y);
右值引用:int&& b = 8;
常量左值引用可以通过左值、右值、左值引用、右值引用初始化。【const 左值引用可以左值,也可以引用右值(因为右值通常是不可以改变的值,所以用 const 左值引用是可以的)】
int num = 9; // num 为左值
const int& c = num; // 使用常量左值引用引用一个常量(不能修改其值)并且该引用本身也是不可修改的
const int& e = 10; // (因为右值通常是不可以改变的值,所以用 const 左值引用是可以的)
常量右值引用只能通过右值初始化
const int&& d = 8;
//const int&& d = b; // error
左值可以通过 move(左值)来转化为右值引用,继而使用右值引用。
int main()
{
// 左值引用只能引用左值,不能引用右值。
int a = 10;
int& ra1 = a; // ra1为 a 的别名
//int& ra2 = 10; // 编译失败,因为10是右值
// const左值引用既可引用左值,也可引用右值。
const int& ra3 = 10;
const int& ra4 = a;
//右值引用只能右值,不能引用左值。
int&& 1 = 10;
int a = 10;
int&& r2 = a; // message : 无法将左值绑定到右值引用
//右值引用可以引用 move 以后的左值
int&& r3 = std::move(a);
return 0;
}
右值直接使用右值引用接收就行,为什么要允许 const 左值引用能引用右值?这个规定为什么会存在呢?
我们都知道,函数传参会生成临时变量,有时我们为了减少拷贝,会将函数参数设置为引用传参,那如果只写成普通的引用传参,那今后我们调用该函数,只能传递左值,无法传入右值。那在C++11 之前,没有右值引用,只能使用 const引用传参来接收实参。所以,当传入引用传参时,建议加上 const。
左值引用总结:
- 左值引用只能引用左值,不能引用右值。
- 但是 const 左值引用既可以引用左值,也可以引用右值。
- 为什么 const 左值引用 可以引用右值呢?-- 左值引用后,可以通过该变量改变引用的右值。所以加上 const,该变量就不能被改变,所以 const 左值引用就能引用右值。因为 const 能够保证被引用的数据不会被修改,维持了权限。
右值引用总结:
- 右值引用只能引用右值,不能引用左值。
- 但是右值引用可以引用 std::move<> 后的左值(当需要右值引用引用一个左值时,可以通过 std::move<> 函数将左值转化为右值引用)。
左值引用和右值引用引出
左值引用的意义在于:
但是左值引用却没有彻底的解决问题:当函数返回对象是一个局部对象,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。函数传返回值时,如果返回值是出了作用域销毁的(出了作用域不存在的),那还需要多次的拷贝构造,导致消耗较大,效率较低。所以这也就是为什么出现了右值引用,当然这是是右值引用价值中的一个!
那在没有右值引用之前,我们是如何解决函数传返回值的拷贝问题呢?答案是:通过输出型参数。
#include
#include
using namespace std;
//给一个数,去构建一个杨辉三角
//如果是函数返回值去解决,那么拷贝消耗是非常大的
vector> generate(int numRows) {
vector> vv(numRows);
for (int i = 0; i < numRows; ++i)
{
vv[i].resize(i + 1, 1);
}
for (int i = 2; i < numRows; ++i)
{
for (int j = 1; j < i; ++j)
{
vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
}
}
return vv;
}
//在没有右值引用之前,我们可以通过"输出型参数"来解决这个问题
//在没有右值引用之前,我们可以通过"输出型参数"来解决这个问题
void generate_new(int numRows, vector> vv) {
vv.reserve(numRows);
for (int i = 0; i < numRows; ++i)
{
vv[i].resize(i + 1, 1);
}
for (int i = 2; i < numRows; ++i)
{
for (int j = 1; j < i; ++j)
{
vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
}
}
return;
}
当然这种方法还是有局限性的,而且平时也不会经常使用,所以很有必要去了解右值引用的强大解法!!
右值引用的价值:
1 补齐左值引用的短板 ---- 通过 右值引用 实现 move 语义 ( move constructor & move assignment operator 移动构造和移动赋值运算符)(尤其是自定义类型数据做参数,或者局部变量作为返回值。)
2 万能引用和完美转发
如下 void fun(T && t); 中 T&& 并不是万能引用,因为 T 的类型在模板实例化时已经确定,当实例函数 void fun(T && t); 时 T 的类型已经确定。
template
class A
{
void fun(T&& t); // 这里是右值引用
};
这里是万能引用(函数模板):
一个模板参数 T,T&& 不是类型为 T 的右值引用,语法规定 T&& 为万能引用,万能引用能接收左值和右值。
template
class A
{
template
void fun(U&& u); // 这里是万能引用
};
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const右值引用" << endl; }
template
void PerfectForward(T&& t)
{
Fun(t);
}
int main()
{
int a = 10;
PerfectForward(a); // 左值
PerfectForward(move(a)); // 右值
const int b = 10;
PerfectForward(b); // const左值
PerfectForward(move(b)); // const右值
return 0;
}
万能引用既可以接收左值也可以接收右值,也可以接收左值引用和右值引用,所以大大的简化了函数个数,比如上例写一个模板函数就够了。
t 既能引用左值,也能引用右值。这种现象也被称作为引用折叠。
引用折叠的原则就是一个右值引用绑定到一个右值引用的时候会变成一个右值引用,其他种类的引用都是左值引用。
引用折叠的规则:
翻译出来就是下面的形式:
& + & = &
& + && = &
&& + & = &
&& + && = &&
完美转发的真正含义就是在多次转发函数参数的时候不丢失参数的左右值属性。即使 T 被折叠成左值,forward<> 也能将其属性转为右值。
【Modern C++】深入理解左值、右值 (qq.com)
在 C++11 之前,引用分为左值引用和常量左值引用两种,但是自 C++11 起,引入了右值引用,也就是说,在 C++11 中,包含如下3中引用:
左值引用
常量左值引用(不希望被修改)
右值引用
在 C++11 中引入了右值引用,因为右值的生命周期很短,右值引用的引入,使得可以延长右值的生命周期。在 C++ 中规定,右值引用是 && 即由2个 & 表示,而左值引用是一个 & 表示。右值引用的作用是为了绑定右值
。
在这里,我们需要特别注意的一点就是右值引用虽然是引用右值,但是其本身是左值。
int&& i = 1;
cout << "i = " << i << " at address = "<< hex << &i << endl; // i = 1 at address = 0x7ffd7adbe584
在上述代码中,a 是一个右值引用,但是其本身是左值,合适因为:
a 出现在等号(=)的左边
可以对 a 取地址
一个表达式有两个属性,分别为类型和值类别。本节说的左值引用和右值引用就属于类型
,而左值和右值则属于值类别范畴
,这个概念很重要,千万不能混淆。
有左值引用,const 左值引用;右值引用,但却没有提到 const 右值引用。
需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可
以取到该位置的地址。(右值被右值引用以后就成为了左值)。
例如: 不能取字面量 10 的地址,但是 rr1 引用后,可以对 rr1 取地址,也可以修改 rr1。如果不想rr1 被修改,可以用 const int&& rr1 去引用。
int main()
{
double x = 1.1, y = 2.2;
int&& rr1 = 10;
const double&& rr2 = x + y;
rr1++;
//rr2++; //不可以修改
cout << &rr1 << endl;
cout << &rr2 << endl;
return 0;
}
完美转发:
完美转发指可以写一个接受任意实参的函数模板,并转发到其它函数,目标函数会收到与转发函数完全相同的实参,转发函数实参是左值那目标函数实参也是左值,转发函数实参是右值那目标函数实参也是右值。那如何实现完美转发呢,答案是使用std::forward()。
返回值优化:
返回值优化 (RVO) 是一种 C++ 编译优化技术,当函数需要返回一个对象实例时候,就会创建一个临时对象并通过复制构造函数将目标对象复制到临时对象,这里有复制构造函数和析构函数会被多余的调用到,有代价,而通过返回值优化,C++ 标准允许省略调用这些复制构造函数。
那什么时候编译器会进行返回值优化呢?
Return Value Optimization | Shahar Mike's Web Spot
不要对函数返回的具名局部变量做 std::move 操作: 在 C++11 之前,返回一个本地对象意味着这个对象会被拷贝,除非编译器发现可以做返回值优化(named return value optimization,NRVO),能把对象直接构造到调用者的栈上。从 C++11 开始,返回值优化仍可以发生,但在没有返回值优化的情况下,编译器将试图把本地对象移动出去,而不是拷贝出去。这一行为不需要程序员手工用 std::move 进行干预——使用std::move 对于移动行为没有帮助,反而会影响返回值优化。
总结:
T
,universal references 总是以 T&&
的形式出现。static_cast
转换,对于要使用右值引用的地方使用 std::move,对于要使用万能引用的地方使用 std::forward -- 希望在函数传递参数的时候,可以保存参数原来的 lvalueness 或 rvalueness,即是说把参数转发给另一个函数。【C++】右值引用(极详细版)_c++ 右值引用_The s.k.y.的博客-CSDN博客
C++笔记四(右值引用)_nanjono的博客-CSDN博客
左值引用、右值引用、移动语义、完美转发,你知道的不知道的都在这里 - 知乎 (zhihu.com)