为什么这两个右值引用示例具有不同的行为?
Why this two rvalue references examples have different behavior?
第一个例子
int a = 0;
auto && b = ++a;
++a;
cout << a << b << endl;
打印22
第二个例子
int a = 0;
auto && b = a++;
++a;
cout << a << b << endl;
打印20
问题:
为什么在第一个例子中第 3 行的 ++a
也递增 b
,为什么在第二个例子中没有这样的行为?
更新: 出现。
因为pre-increment(++a
)先自增a
的值,存储结果,然后然后 returns引用至 a
。现在 a
和 b
实际上指向同一个对象。
Post-increment(a++
),然而,先把a
的当前值存储在一个临时的,递增a
,然后returns这个临时的 - 你的右值 ref 指向它。 a
和 b
指向不同的对象,更具体地说 - b
是在递增之前临时保存 a
的值。
这就是为什么对于迭代器和其他定义增量/减量的复杂对象,鼓励使用 ++it
而不是 it++
的原因:后者创建一个临时副本,因此 可能会慢一些。
在第二种情况下(post-增量)b实际上引用了为(a++)创建的临时文件,所以增量不影响b。
区别在于 ++a
是左值,而 a++
不是。这是由 C++14 [expr.pre.incr]/1:
指定的
The operand of prefix ++
is modified by adding 1 [...] The
operand shall be a modifiable lvalue. [...] The result is the updated operand; it is an lvalue
和[expr.post.incr]/1:
[...] The result is a prvalue.
现在我们考虑 auto && b = ++a;
。 ++a
是一个左值。 auto&&
是转发参考。转发引用实际上可以绑定到左值:auto
本身可以推断为引用类型。此代码推断为 int &b = ++a;
.
当引用绑定到相同类型的左值时,引用直接绑定,所以b
成为a
的别称。
在第二个例子中,auto && b = a++;
、a++
是纯右值。这意味着它没有关联地址,并且不再与变量 a
有任何关系。此行与 ++a; auto && b = (a + 0);
具有相同的行为。
首先,由于 a++
是纯右值,auto&&
推导为 int&&
。 (即 auto
推断为 int
)。当非 class 类型的引用绑定到纯右值时,将从该值复制初始化一个临时对象。此对象已延长其生命周期以匹配引用。
所以在第二种情况下 b
绑定到与 a
不同的对象,一个 "temporary" int(这不是真的那么临时,因为它持续到 b
确实如此)。
参考绑定规则在[dcl.init.ref].
第一个例子
int a = 0;
auto && b = ++a;
++a;
cout << a << b << endl;
打印22
第二个例子
int a = 0;
auto && b = a++;
++a;
cout << a << b << endl;
打印20
问题:
为什么在第一个例子中第 3 行的 ++a
也递增 b
,为什么在第二个例子中没有这样的行为?
更新:
因为pre-increment(++a
)先自增a
的值,存储结果,然后然后 returns引用至 a
。现在 a
和 b
实际上指向同一个对象。
Post-increment(a++
),然而,先把a
的当前值存储在一个临时的,递增a
,然后returns这个临时的 - 你的右值 ref 指向它。 a
和 b
指向不同的对象,更具体地说 - b
是在递增之前临时保存 a
的值。
这就是为什么对于迭代器和其他定义增量/减量的复杂对象,鼓励使用 ++it
而不是 it++
的原因:后者创建一个临时副本,因此 可能会慢一些。
在第二种情况下(post-增量)b实际上引用了为(a++)创建的临时文件,所以增量不影响b。
区别在于 ++a
是左值,而 a++
不是。这是由 C++14 [expr.pre.incr]/1:
The operand of prefix
++
is modified by adding 1 [...] The operand shall be a modifiable lvalue. [...] The result is the updated operand; it is an lvalue
和[expr.post.incr]/1:
[...] The result is a prvalue.
现在我们考虑 auto && b = ++a;
。 ++a
是一个左值。 auto&&
是转发参考。转发引用实际上可以绑定到左值:auto
本身可以推断为引用类型。此代码推断为 int &b = ++a;
.
当引用绑定到相同类型的左值时,引用直接绑定,所以b
成为a
的别称。
在第二个例子中,auto && b = a++;
、a++
是纯右值。这意味着它没有关联地址,并且不再与变量 a
有任何关系。此行与 ++a; auto && b = (a + 0);
具有相同的行为。
首先,由于 a++
是纯右值,auto&&
推导为 int&&
。 (即 auto
推断为 int
)。当非 class 类型的引用绑定到纯右值时,将从该值复制初始化一个临时对象。此对象已延长其生命周期以匹配引用。
所以在第二种情况下 b
绑定到与 a
不同的对象,一个 "temporary" int(这不是真的那么临时,因为它持续到 b
确实如此)。
参考绑定规则在[dcl.init.ref].