c++ 有一个右值引用变量是什么意思?

m3eecexj  于 2023-07-01  发布在  其他
关注(0)|答案(2)|浏览(129)

我想我理解带有签名的函数,比如:

void f(std::string&&):

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

std::string t();

std::string&& s = t();

其中变量初始化为右值ref。(这里我写了t来返回值,但如果通过l/rvalue引用返回时的行为不同,请有兴趣知道)。
通常,我在关于C++的帖子/谜题中看到这一点,而不是生产代码。
我有几件事想了解一下
1.这种行为的规则是什么?
1.与通过值或左值常量引用捕获相比,这给你带来了什么好处?在我看来,如果你通过值捕获,移动/复制将被省略,你将能够修改它(不像const-ref)。我可能是不正确的,但如果是这样的话,我看不出什么好处右值引用将在这里
1.当t返回一个右值引用时,它的行为是否不同?返回右值引用的好理由是什么?我猜std::move可以,但它还有什么好处呢?

ssm49v7z

ssm49v7z1#

右值引用可以绑定到两个对象:

  • 一个xvalue,在这种情况下,我们只是引用了一些东西,但是引用的类型告诉我们,如果我们想的话,我们可以从它移动
  • 一个右值,在这种情况下,我们正在实现一些东西

如果您不熟悉价值类别或需要快速复习,这里有一个快速概述:
| | movable | immovable |
| --|--| ------------ |
| xvalue -std::move(x)等。|左值-x"str"等||
| prvalue -1 + 2sqrt(2)等。|||

  • xvalues* 和 prvalues 统称为 rvalues,这些是右值引用可以绑定的对象。

Rvalue引用xvalues

// (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;

// ...

在这种情况下,我们只是将引用绑定到其他对象。如果我们使用abc,它们在大多数上下文中都是左值,与std::string&这样的左值引用相比几乎没有区别。
然而,该类型携带abc引用可移动的东西的信息,我们可以使用std::movestd::forward将引用转换回x值。这将允许传递引用,然后稍后调用移动构造函数。

返回右值引用

当从函数返回右值引用时(例如std::move),它会变成一个xvalue。这就是大多数情况下创建右值对x值的引用的方式。下面是几个例子:

右值对纯右值的引用

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

std::string&& r = t(); // materialize temporary object returned by t()

在本例中,我们引用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
}

如果没有临时物化,当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());

像这样的例子就是为什么你不具体化临时值,如果你不需要。创建右值引用是出于必要,而不是因为它可以提高性能或代码质量。

des4xlb0

des4xlb02#

1.这种行为的规则是什么?
如果t按值返回,您将获得返回的任何内容的副本(尽管副本可能被省略)。右值引用延长了该副本的生存期,但与普通变量相比没有任何好处。See also
如果t通过左值引用返回,则代码无法编译,因为右值引用无法绑定到它。
如果t通过右值引用返回,您将获得对返回内容的引用。如果t返回一个局部变量,那么您将得到一个悬空引用,因为该变量在调用之后就消失了。否则,您得到的引用的行为或多或少类似于左值引用,就好像t是由左值引用返回的。
1.与通过值或左值常量引用捕获相比,这给你带来了什么好处?在我看来,如果你通过值捕获,移动/复制将被省略,你将能够修改它(不像const-ref)。我可能是不正确的,但如果是这样的话,我看不出什么好处右值引用将在这里
不要将lambda表达式中使用的“捕获”与函数调用的简单赋值过程相混淆。Copy (and move) elision仅在函数返回局部变量(或局部表达式)时适用。如果t返回其他内容,例如解引用某个内容所产生的值,或t外部的其他对象的成员变量,则不可能有任何复制省略。在这些情况下,按值返回会产生一个副本,而按引用返回则不会。
1.当t返回一个右值引用时,它的行为是否不同?返回右值引用的好理由是什么?我猜std::move可以,但它还有什么好处呢?
对于自由函数,通过右值引用返回没有什么意义。我能想到的唯一用例是当函数从参数接收对象并返回它或其中的某些内容时,并期望调用者从那里移动。对于一个成员函数,它可以是有用的,例如只是移动一个成员变量。See also

相关问题