C++中左值和右值的理解
先看看C++ Primer的解释:
C++的表达式要不然是右值,要不然就是左值。这两个名词是从C语言继承过来的,原本是为了帮助记忆:左值可以位于赋值语句的左侧,右值则不能。
在C++语言中,二者的区别就没那么简单了。一个左值表达式的求值结果是一个对象或者一个函数,然而以常量对象为代表的某些左值实际上是不能作为赋值语句的左侧运算对象。此外,虽然某些表达式的求值结果是对象,但它们是右值而非左值。可以做一个简单的归纳;当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。
使用关键字
decltype
的时候,左值和右值也有所不同。如果表达式的求值结果是左值,decltype
作用于该表达式(不是变量)得到一个引用类型。举个例子,假定p
的类型是int*
,因为解引用运算符生成左值,所以decltype(*p)
的结果是int&
。另一方面,因为取地址运算符生成右值,所以decltype(&p)
的结果int**
,也就是说,结果过是一个指向整数指针的指针。
右值是值。一些字面值常量是右值,例如1
。这样的右值可以不需要内存,如果编译成汇编来看,1
可能仅仅是存在寄存器里面的,并没有使用任何内存。
但是并不是所有右值都是没有内存的。例如函数返回的非引用对象,这样的对象称为亡值。这样的对象相当于一块没有名字的临时内存,除了在返回处马上被用来初始化一个新对象,过了这一句后没有任何方法可以访问到它,语义上就像值一样。被用完后这个临时对象马上就会被销毁。(当然,有RVO优化,返回值不一定会创建一个临时对象,而是会通过栈直接传递对象,减少一次拷贝构造的调用)。
因此不能简单地用有没有地址,能放在等号哪侧来判断左右值。要看语义上表达式结果是对象(左值)还是值(右值)。例如:返回类型为引用类型代表结果是左值,非引用类型代表结果是右值;解引用结果是左值,取地址结果是右值。 ## 右值引用
和左值引用一样,右值引用也是一种别名。区别在于,左值引用只能引用左值;右值引用只能引用右值。
右值引用相当于给无名的临时对象起了个名字。一个块有名字的内存,它和变量没有什么区别。事实上,右值引用就是变量,你甚至可以对它取地址。即使是对1
这样的不需要内存的字面值,其右值引用会为其创建一个内存空间,并和引用名绑定起来。因此右值引用本身是左值也不出奇了。
右值引用延长了右值的生命周期,将右值(临时对象)的寿命和右值引用名的寿命绑定起来了。为的就是可以在移动语义下对临时对象进行操作,执行移动构造等操作。右值引用相当于一个承诺,承诺它引用的对象(或其资源)马上就会被销毁(比如右值引用名的寿命结束后),不再会被别人使用,因此你才可以自由接管这个对象的资源,而无需拷贝。
如果使用std::move
,可以将一个左值当成右值来处理,这样也就可以将其用在移动语义下了。此时这个左值的生命周期并不会随着右值引用名寿命结束而结束,因此被移动后的左值可能会失去它原有的资源(比如被移动后的string
字符串会变成空串),这是右值引用的“承诺”带来的。
C++中左值和右值的理解