摘自两本书《c++程序设计语言》(Bjarne Strousstrup)和《c++Primer》第五版
我们可以分配并使用没有名字的对象(比如用new创建的对象),也能为某些看起来不太寻常的表达式赋值(如,*p[a+10]=7)。因此,我们需要用一个名字来表示“内存中的某些东西”。这就是对象一词最简单最基本的含义。 换句话说,对象(object)是指一块连续的存储区域,左值(lvalue, left value)是指向对象的一条表达式。“左值”的字面意思是“能用在赋值运算符左侧的东西”,但其实不是所有的左值都能用在赋值运算符的左侧,左值也有可能表示某个常量。未被申明成const的左值称为可修改的左值(modifiable lvalue)。
6.4.1 左值和右值
为了补充和完善左值的含义,我们相应地定义了右值(rvalue, right value)。简单来说,右值是指“不能作为左值的值”,比如像函数返回值一样的临时值。
左值和右值
c++的表达式要不然是右值(rvalue,读作"are-value”),要不然就是左值(lvalue,读作“ell-value”)。这两个名词是从c语言继承过来的,原本是为了帮助记忆:左值可以位于赋值语句的左侧,右值则不能。
在c++语言中,二者的区别就没有那么简单了。一个左值表达式的求值结果是一个对象或者一个函数,然而以常量对象为代表的某些左值实际上不能作为赋值语句的左侧运算对象。此外,虽然某些表达式的求值结果是对象,但他们是右值而非左值。可以做一个简单的归纳:当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。
不同的运算符对运算对象的要求各不相同,有的需要左值运对象、有的需要右值运算对象;返回值也有差异,有的得到左值结果、有的得到右值结果。一个重要的原则(参见13.6节,第470页将介绍一种例外的情况)是在需要右值的地方可以用左值来代替,但是不能把右值当成左值(也就是位置)使用。当一个左值被当成右值使用时,实际使用的是他的内容(值)。到目前为止,已经有几种我们熟悉的运算符是要用到左值的。
使用关键字decltype的时候,左值和右值也有所不同。如果表达式的求值结果是左值,decltype作用于该表达式(不是变量)得到一个引用类型。举个例子,假定p的类型是int*,因为解引用运算符生成左值,所以decltype(*p)的结果是int&。 另一方面,因为取地址运算符生成右值,所以decltype(&p)的结果是int**,也就是说,结果是一个指向整数型指针的指针。
13.6.1
右值引用
为了支持移动操作,c++新标准引入了一种新的引用类型--右值引用。 右值引用有一个重要的性质--只能绑定到一个将要销毁的对象。因此,我们可以自由地将一个右值引用的资源“移动”到另一个对象中。
左值持久;右值短暂
考虑左值和右值表达式的列表,两者相互区别之处就很明显了:左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。
由于右值引用只能绑定到临时对象,我们得知
这两个特性意味着:使用右值引用的代码可以自由地接管所引用的对象的资源。
变量是左值
变量可以看做只有一个运算对象而没有运算符的表达式,虽然我们很少这样看待变量。类似其他任何表达式,变量表达式也有左值/右值属性。变量表达式都是左值。带来的结果就是,我们不能将一个右值引用绑定到一个右值引用类型的变量上。
- int &&rr1 = 42; // 正确:字面常量是右值
- int &&rr2 = rr1; // 错误: 表达式rr1是左值!
其实有了右值表示临时对象这一观察结果,变量是左值这一特性并不惊讶。毕竟,变量是持久的,直至离开作用域时才被销毁。
变量是左值,因此我们不能将一个右值引用直接绑定到一个变量上,即使这个变量是右值引用类型也不行。
7.7
引用:
为了体现左值/右值以及const/非const的区别,存在三种形式的引用:
7.7.1 左值引用
在类型名字中,符号X&的意思是“X的引用”;它常用于表示左值的引用,因此称为左值引用。
- double& dr =1; //错误:此处需要左值
- const double& cdr{1}; // ok 这条语句可以理解为 : double temp = double{1}; 临时变量, const double& cdr{temp}; 用这个临时变量作为cdr的初始值。
7.7.2 右值引用
c++ 之所以设计了几种不同形式的引用,是为了支持对象的不同用法:
右边值引用使用: 3.3.2
在移动构造函数中作为参数:
- class Vector{
- //...
- Vector(const Vector& a); //拷贝构造函数
- Vector& operator=(const Vector& a); // 拷贝赋值运算符
-
- Vector(Vector&& a); // 移动构造函数
- Vector& operator=(Vector&& a); // 移动赋值运算符
- };
移动构造函数不接受const实参:毕竟移动构造函数最终要删除掉它实参中的值。移动赋值运算符的定义与之类似。 (在将对象作为函数返回值的时候,编译器将选择移动构造函数来执行从函数中移出返回值的任务。)