C++11:为什么允许赋值 rvalues?

6

据我所知,将rvalue引用从函数返回是危险的原因是由于以下代码:

T&& f(T&& x) { do_something_to_T(x); return static_cast<T&&>(x); }
T f(const T& x) { T x2 = x; do_something_to_T(x2); return x2; }
T&& y = f(T());

这将导致y成为未定义的悬空引用。
然而,我不明白上述代码为什么会编译?是否有合法的原因将rvalue引用赋给另一个rvalue引用?rvalues难道不应该是“临时”的吗,也就是说,在表达式结束时将变得无效?能够给它们赋值似乎有些愚蠢。

Eric Lippert在这篇文章中谈到了实现允许ref返回的C#变体,并且还涵盖了许多相关的用例。 - Adam Mihalcin
哪个编译器编译它?o.O...因为它不能编译-你不能用T初始化T && - lapk
2
@AzzA:嗯,你可以用一个rvalue初始化一个rvalue引用,这就是它们存在的意义。 - Cat Plus Plus
1
此外,这是通常的引用绑定。你必须能够绑定引用,所以这个问题似乎有点愚蠢。 - Cat Plus Plus
我已经添加了 static_cast<T&&>(x),所以现在它可以编译。 - Clinton
显示剩余2条评论
1个回答

11

难道rvalue不应该是"临时的"吗,也就是说,在表达式结束时将变得无效吗?

不,它们不是。

假设你有一个函数 f,这个也是合法的:

T t{};
T&& y = f(std::move(t));

这是完全有效的C++11代码。并且发生的情况也是定义明确的。
你的代码之所以未定义是因为传递了一个临时对象。但是,r-value引用不一定是临时对象。
更详细地说,r-value引用在概念上是对一个值的引用,从中可以进行某些操作,而这些操作在其他情况下是不被允许的。
C++11非常仔细地规定了r-value引用。l-value引用可以绑定到任何非临时对象,无需进行任何强制转换或其他操作:
T t{};
T &y = t;

一个右值引用只能隐式地绑定到一个临时对象或其他的“xvalue”(即一个很可能在不久的将来就会消失的对象):
T &&x = T{};
T &&no = t; //Fail.

为了将一个右值引用绑定到非 xvalue,你需要进行显式转换。C++11 中的这种转换方式很明显,使用 std::move
T &&yes = std::move(t);

我所说的“特定操作”是“移动”。只有在以下两种情况下才可以从对象中移动:
1. 它将要离开。例如:临时变量。 2. 用户明确表示要从它移动。
这些是唯一两种r值引用可以绑定到某些东西的情况。
r值引用存在的唯二原因是支持移动语义和支持完美转发(需要一种新的引用类型,可以将其钩子式转换机制挂接到其中,以及潜在的移动语义)。因此,如果没有进行这两个操作之一,则使用“&&”的原因是可疑的。

如果f是一个合法的函数,但不应该在临时对象上工作,那么它的定义应该是:T& f(T&),这样当接收到临时对象时就会抛出编译错误,而不是导致未定义的行为。如果不是临时对象,为什么不使用普通引用?你可以从普通引用中移动。 - Clinton
@Clinton:是的,你可以从一个左值引用移动。但是那样你就不能传递临时对象了。你假设你的选择是“只接受临时对象的函数”和“无法接受临时对象的函数”。为什么不允许函数接受任何类型的参数,并让它根据所给的参数进行处理呢?这对双方都更容易些。 - Nicol Bolas
1
@Clinton:此外,查看此答案以了解如何处理参数中的移动/复制。 - Nicol Bolas
@Nicol 是的,我知道这篇文章的发布时间很久了;)但是在思考了你的陈述之后,它确实让我信服 - 我只是想得到确认。那么,是什么让你今天不再同意呢? - Reizo
@Nicol,你是否仍然同意我指出的特定情况,但发现你的更一般的陈述不再那么可行了? - Reizo
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接