这是用于解释左值和右值之间差异的最常见的“经验法则”之一。
C++ 中的情况比这复杂得多,因此这只能是经验法则。我将尝试回顾几个概念,并尝试阐明为什么这个问题在 C++ 世界中如此复杂。首先让我们回顾一下从前发生的事情
一开始有C
首先,在一般编程语言的世界中,“左值”和“右值”最初意味着什么?
在 C 或 Pascal 等更简单的语言中,这些术语用来指代可以放置在L快速或在R赋值运算符的权限。
在像 Pascal 这样的语言中,赋值不是表达式而只是语句,差异非常明显,并且是用语法术语定义的。左值是变量的名称或数组的下标。
这是因为只有这两件事可以位于作业的左侧:
i := 42; (* ok *)
a[i] := 42; (* ok *)
42 := 42; (* no sense *)
在 C 中,同样的差异也适用,并且它仍然非常符合语法,因为您可以查看一行代码并判断表达式是否会生成左值或右值。
i = 42; // ok, a variable
*p = 42; // ok, a pointer dereference
a[i] = 42; // ok, a subscript (which is a pointer dereference anyway)
s->var = 42; // ok, a struct member access
那么 C++ 发生了什么变化呢?
小语言长大了
在 C++ 中,事情变得更加复杂,差异不再是语法上的,而是涉及类型检查过程,有两个原因:
- 所有内容都可以保留在赋值的左侧,只要其类型具有合适的重载
operator=
- 参考
因此,这意味着在 C++ 中,您不能仅通过查看表达式的语法结构来判断表达式是否会生成左值。例如:
f() = g();
是一个在 C 中没有意义的语句,但在 C++ 中可能完全合法,例如,f()
返回一个引用。表达方式就是这样v[i] = j
为。。。工作std::vector
: the operator[]
返回对该元素的引用,以便您可以为其赋值。
那么区分左值和右值还有什么意义呢?当然,这种区别仍然与基本类型相关,但也决定了什么可以绑定到非常量引用.
那是因为你不想有这样的法律代码:
int &x = 42;
x = 0; // Have we changed the meaning of a natural number??
因此,该语言仔细指定什么是左值,什么不是,然后说只有左值可以绑定到非常量引用。所以上面的代码是不合法的,因为整数文字不是左值,所以非常量引用不能绑定到它。
注意const引用是不同的,因为它们可以绑定到文字和临时变量(本地引用甚至可以延长这些临时变量的生命周期):
int const&x = 42; // It's ok
到目前为止,我们只触及了 C++98 中已经发生的事情。这些规则已经比“如果它有名称,它就是左值”更复杂,因为您必须考虑引用。因此返回非常量引用的表达式仍被视为左值。
此外,这里提到的其他经验法则并不适用于所有情况。例如“如果你可以获取它的地址,那么它就是一个左值”。如果“获取地址”是指“申请operator&
”,那么它可能会起作用,但不要欺骗自己认为您永远无法获得临时地址:this
例如,临时成员函数内的指针将指向它。
C++11 发生了什么变化
C++11 通过添加 an 的概念,使 bin 更加复杂右值引用,即即使是非 const 也可以绑定到右值的引用。事实是它可以only应用于右值使其既安全又有用。我认为不需要解释为什么右值引用有用,所以继续吧。
这里的要点是,现在我们有更多的案例需要考虑。那么现在什么是右值呢?该标准实际上区分了不同类型的右值,以便能够在存在右值引用的情况下正确说明右值引用的行为以及重载解析和模板参数推导。所以我们有这样的术语xvalue
, prvalue
诸如此类的事情,让事情变得更加复杂。
我们的经验法则又如何呢?
因此,“所有有名称的东西都是左值”仍然可以是正确的,但可以肯定的是,每个左值都有名称是不正确的。返回非常量左值引用的函数是左值。按值返回某些内容的函数会创建一个临时值,并且它是一个右值,返回右值引用的函数也是如此。
那么“临时变量是右值”呢?这是真的,但也可以通过简单地转换类型将非临时值变成右值(就像std::move
).
所以我认为,如果我们牢记它们是什么:经验法则,所有这些规则都是有用的。
它们总会有一些不适用的极端情况,因为要准确指定右值是什么和不是,我们无法避免使用标准中使用的确切术语和规则。这就是它们被写的原因!