拥有右值引用变量意味着什么?

Ant*_*ole 4 c++ rvalue-reference move-semantics

我想我理解具有如下签名的函数:

void f(std::string&&):
Run Code Online (Sandbox Code Playgroud)

它将应用于右值以重用其资源。然而,我偶尔会看到这样的代码:

std::string t();

std::string&& s = t();
Run Code Online (Sandbox Code Playgroud)

其中变量被初始化为右值引用。(这里我写了 t 按值返回,但如果按左/右值引用返回时行为不同,有兴趣知道)。

通常,我在有关 C++ 的帖子/谜题中看到这一点,而不是在生产代码中。

我有几点想了解一下:

  1. 这种行为的规则是什么?
  2. 与通过值或左值常量引用捕获相比,这有什么优势?在我看来,如果您按值捕获,则移动/复制将被省略,并且您将能够修改它(与 const-ref 不同)。我对此可能是不正确的,但如果是这样的话,我看不出右值引用在这里会有什么好处
  3. 当 t 返回右值引用时,其行为是否有所不同?返回右值引用的充分理由是什么?我猜 std::move 可以,但是它在什么时候会有用呢?

Jan*_*tke 7

右值引用可以绑定到两个东西:

  • 一个 xvalue,在这种情况下,我们只是引用某个东西,但引用的类型告诉我们,如果我们想要的话,我们可以从它移开
  • 纯右值,在这种情况下我们正在具体化一些东西

如果您不熟悉值类别或需要快速复习,请参阅以下快速概述:

活动 不动的
有身份 x值 -std::move(x) 左值 - x"str"
匿名的 纯右值 - 1 + 2sqrt(2)

xvaluesprvalues统称为rvalues,这些是右值引用可以绑定的东西。

右值对 x 值的引用

// (a) returning an rvalue reference produces an xvalue
std::string&& t();
std::string&& a = t();

// (b) elements of an rvalue array are xvalues
std::string array_of_strings[N];
std::string&& b = std::move(array_of_strings)[0];

// (c) members of an rvalue struct are xvalues
struct wrapper { std::string str; };
wrapper wrap;
std::string&& c = std::move(wrap).str;

// ...
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们只是将引用绑定到某个其他对象。如果我们使用ab、 或c,它们在大多数情况下都是左值,并且与像 之类的左值引用相比几乎没有什么区别std::string&

然而,该类型携带的信息是ab、 和c引用可移动的东西,我们可以使用std::movestd::forward将引用转回 xvalue。这将允许传递引用,然后稍后调用移动构造函数。

返回右值引用

当从函数(例如std::move)返回右值引用时,它会变成 xvalue。这就是大多数时候创建对 xvalue 的右值引用的方式。这里还有几个例子:

  • std::forward如果使用右值调用,有时会返回右值引用
  • std::move_iterators 有一个*返回右值引用的运算符
  • std::get(std::tuple)当元组是右值时,返回对元组成员的右值引用

另请参阅:函数返回右值引用是否有意义?

右值对纯右值的引用

// returns an object, so t() is prvalue
std::string t();

std::string&& r = t(); // materialize temporary object returned by t()
Run Code Online (Sandbox Code Playgroud)

在本例中,我们指的是 . 返回的临时对象t()。这段代码乍一看是错误的,但实际上是有效的,因为该临时对象的生命周期扩展到了r.

进行临时物化是因为它使通用编程变得更容易。我们经常在使用转发引用时创建右值引用(例如auto&&)。

// this works regardless of whether the * operator of the iterator returns:
//   - prvalue, e.g. std::vector<bool>::reference
//   - lvalue, e.g. int&
//   - xvalue, e.g. int&& from a std::move_iterator
for (auto&& e : container)
{
    use(e); // TODO: perfect forwarding, if necessary
}
Run Code Online (Sandbox Code Playgroud)

e如果没有临时具体化,此代码在初始化为纯右值时将具有未定义的行为,因为我们将立即创建一个悬空引用。在该用例之外,它没有任何意义,因为物化并不比仅仅存储对象更好。物化实际上对性能来说更糟糕(见下文)。

注意:右值引用函数参数也会发生临时物化,在这种情况下它很有帮助。

悲观化

请注意,通过右值引用具体化纯右值并不能让我们的代码更快;这不是一个优化技巧。如果可能的话,最好只传递纯右值。例如:

std::string t();
void consume(std::string s);

// BAD, results in one extra move constructor call
std::string&& r = t();
consume(std::move(r));

// GOOD, the result of t() and and the argument to consume() are the same
// object, thanks to mandatory copy elision
consume(t());
Run Code Online (Sandbox Code Playgroud)

诸如此类的示例说明了为什么在不必要的情况下不实现临时值的原因。创建右值引用是出于必要,而不是因为它可以提高性能或代码质量。

另请参阅:将局部变量声明为右值引用是否没有用,例如 T&& r = move(v)?