为什么在C++11中有右值引用的情况下还要使用std::move?

72

可能是重复问题:
什么是移动语义?

我最近参加了一个C++11研讨会,听到了以下的建议。

when you have && and you are unsure, you will almost always use std::move

有人能解释一下为什么要使用 std::move 而不是其他替代方案以及使用 std::move 的一些情况吗?


3
@XavierHolt 刚想起来:它不是一个逻辑运算符。 - Michael Todd
11
请务必观看Scott Meyers - Universal References in C++11视频。链接为:http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Scott-Meyers-Universal-References-in-Cpp11。 - fredoverflow
因为,命名的右值引用实际上是左值。 - anilbey
4个回答

109
首先,我要解决问题中可能存在的误解:
每当你在代码中看到T&& t(其中T是一个实际类型,而不是一个模板类型),请记住t的值类别是一个左值(引用),而不再是一个右值(临时对象)。这非常令人困惑。T&&仅仅意味着t是从一个rvalue构造出来的1,但t本身是一个左值,而不是一个右值。如果它有一个名称(在这种情况下,是t),那么它就是一个左值,并且不会自动移动;但如果它没有名称(例如3+4的结果),那么它就是一个右值,并且如果可以的话,将自动移动到它的结果中。类型(在这种情况下是T&&)与变量的值类别(在这种情况下是一个左值)几乎没有关系。

尽管如此,如果你在代码中写了T&& t,那么这意味着你有一个对曾经是临时对象的变量的引用,如果你愿意,可以销毁它。如果你需要多次访问该变量,则不应该从中std::move,否则它将失去其值。但是,在最后一次访问t时,如果你愿意,可以安全地将其值std::move到另一个T中(而且在95%的情况下,这就是你想要做的)。所有这些也适用于auto&&变量。

1. 如果T是一个模板类型,则T&&是一个转发引用,此时你应该使用std::forward<T>(t)而不是std::move(t)来进行最后一次操作。请参见这个问题


8
对于通用引用,你应该使用 std::forward<T>(t) 而不是 std::move(t) - Mooing Duck
5
需要记住的一件事是,如果您看到一个命名变量,那么它不管是被声明为&&(“右值”)&(引用)还是``(拷贝)始终被认为是左值。您可以将真正的右值引用视为仅能从函数(自己的函数或通过std::move()包装的函数,简而言之-返回参数)中返回的临时对象。 - Red XIII
1
@Macke:使用通用引用,您不知道它是rvalue还是lvalue,因此您不知道是否需要将其std::move。如果输入确实是rvalue,则std::forwardstd::move相同,如果输入确实是lvalue,则不执行任何操作,因此您始终获得正确的机制。请参见此问题 - Mooing Duck
@Mehrdad,我不同意,参考折叠规则的使用被普遍称为“通用引用”,就像动态分配内存被称为来自“堆”一样。虽然这不是“官方术语”,但这是常用名称。 - Mooing Duck
3
rvalues是表达式(函数和数学操作)的无名结果。例如,myintx + myinty 的结果本身是一个 int,但它没有名称,因此它将是一个 rvalue。您可以将其传递给期望 rvalue (&&) 的函数,但不能将其传递给期望 lvalue 引用 (&) 的函数。同样,您也无法将 myintx 传递给期望 rvalue (&&) 的函数,因为它有一个名称。要“取消命名”对象,告诉编译器可以安全地移动该对象,请使用 std::move 函数。 - Mooing Duck
显示剩余10条评论

31

我发现这篇文章对于rvalue引用的主题非常有启发性。作者在文章结尾提到了std::move。以下是最相关的引用:

我们需要使用std::move,它位于<utility>中——std::move是一种说法,“好的,老实说我知道我有一个lvalue,但我希望它成为rvalue。” std::move本身并没有移动任何东西;它只是将一个lvalue转换为rvalue,以便您可以调用移动构造函数。


假设你有一个长这样的移动构造函数:

MyClass::MyClass(MyClass&& other): myMember(other.myMember)
{
    // Whatever else.
}

当您使用语句other.myMember时,返回的值是一个左值。因此,该代码使用复制构造函数来初始化this->myMember。但由于这是一个移动构造函数,我们知道other是一个临时对象,因此它的成员也是临时的。因此,我们真正想要使用更高效的移动构造函数来初始化this->myMember。使用std::move确保编译器将other.myMember视为右值引用并调用移动构造函数,正如您所希望的:
MyClass::MyClass(MyClass&& other): myMember(std::move(other.myMember))
{
    // Whatever else.
}

请不要在需要保留的对象上使用std::move - 移动构造函数几乎肯定会破坏传入它们的任何对象。这就是为什么它们只与临时对象一起使用的原因。

希望这有所帮助!


4
当你拥有一个类型为T&&的对象,即rvalue时,这意味着该对象可以安全地进行移动,因为没有其他人会依赖于它的内部状态。
由于移动操作不应该比复制更昂贵,所以你几乎总是想要移动它。而要移动它,你必须使用std::move函数。
即使是安全的,什么时候应该避免使用std::move呢?我不会在简单的例子中使用它,例如:
 int x = 0;
 int y = std::move(x);

除此之外,我看不到任何缺点。如果不会使代码复杂化,我认为应该尽可能地进行移动。
另一个例子是,当你不想移动返回值时。语言保证返回值至少被移动了,因此你不应该写成:
return std::move(x); // not recommended

如果你很幸运,返回值优化会发生,这比移动操作更好。


Scott Meyers认为你甚至应该移动原始类型。 - fredoverflow
@FredOverflow 很有趣。他在他的C++ and Beyond 2012演讲中解释了吗?我只能想到将其用作某种文档,但我并不完全相信这是一个好主意。 - Philipp Claßen
1
他在我作为对问题的评论链接中的演讲中解释了这一点。他说:“你应该毫不犹豫地这样做”。但仅仅因为Scott Meyers这么说,并不意味着每个人都必须无条件遵循,而不加质疑。 - fredoverflow

2

当你需要将一个对象的内容“转移”到另一个地方而不进行复制时,可以使用 move。使用 std::move 还可以使一个对象获取临时对象的内容而不进行复制。

更多信息请参考维基百科上的右值引用和移动构造函数


2
“Checkout this link”不是一个好的链接标签。您能否改进它,使标签描述所链接的内容? - Lightness Races in Orbit

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