计算机

左值和右值

引入

在传统的 C 语言,可以发现有一些对象可以放在等号左边,有一些则只能放在等号右边,例如:

int x, *p = &x, a[2] = {0, 1}, *q;

x = 42;
*p = 19;
a[0] = 1;
q = a;
*(q + 1) = 37;

// x + 1 = 43;
// 12 = *p;
// q + 1 = p + 1;

这就是左值(L value)和右值(R value)最初的命名来源。

经过分析,我们可以发现,以上过程中,C++ 中确实存在两种对象,一种是具体的、有地址的对象,另一种是临时的数值、中间的计算结果。我们发现这种定义更加反映 C++ 对象的生命周期,所以我们把这两者作为现代的 C++ 中左值和右值的定义。

概念

右值的引用

C++ 中,我们可以对一个右值进行引用,方法是使用 T&&。

int x = 42;
int &&y = x + 1;

直觉想象

这一段代码可以从直觉上理解为:右值并不是完全没有地址,只是这个地址只临时存在于内存或者寄存器之中,也就是:

int tmp = x + 1;

而右值引用把 x + 1 计算得到的临时对象 tmp “升格”成了一个“对象”,并把 y 的地址绑定到了这个对象上,这就是右值引用。

实际过程

实际的编译器编译后的代码,如果没有右值引用,这个右值的地址是不可见的,甚至可能被优化掉,根本不存在这个中间结果。

但有了右值引用,这个临时结果 x + 1 就不能被抛弃,它的“生命”被延长到了 y 的作用域。这就是右值引用的作用:

右值引用是一个左值。因为右值引用后,临时结果被固定为了一个有地址的对象,可以取地址,所以是左值。

std::move

这个函数可以把左值转换为右值。

转换过程只是一种重解释,它把一个左值对象重新解释成右值,不会产生新的栈内存或者堆内存。

当 std::move 和右值引用同时出现时,它和普通的引用很类似:

int x = 10;
int &&a = std::move(x); // 等效于 int &a = x;

区别仅在于类型是 int&& 还是 int&,但是两者绑定的内存是一致的。

BEYOND THE ARTICLE

读完了,有什么想法?

阅读已保存的问答示例,并循着引用回到原文。

查看公开问答示例
— 本文完 — 返回文章列表