我应该返回一个右值引用(通过 std::move'ing)吗?
Should I return an rvalue reference (by std::move'ing)?
一篇C++下一篇博客文章说
A compute(…)
{
A v;
…
return v;
}
如果A
有一个可访问的副本或移动构造函数,编译器可以选择删除该副本。否则,如果A
具有移动构造函数,则v
被移动。否则,如果A
具有复制构造函数,则复制v
。否则,将发出编译时错误。
我想我应该总是返回没有std::move
的值因为编译器能够为用户找到最佳选择。但在博客文章的另一个例子中
Matrix operator+(Matrix&& temp, Matrix&& y)
{ temp += y; return std::move(temp); }
这里std::move
是必要的,因为y
必须被视为函数内部的左值。
啊,看完这篇博文,我的脑袋都快炸了。我尽力理解其中的道理,但我学得越多,就越困惑。为什么我们要在std::move
的帮助下返回值?
所以,假设您有:
A compute()
{
A v;
…
return v;
}
你在做:
A a = compute();
此表达式中涉及两种传输(复制或移动)。首先,函数中由v
表示的对象必须转换为函数的结果,即compute()
表达式提供的值。让我们称之为转移1。然后,这个临时对象被转移以创建由a
-Transfer 2表示的对象。
在许多情况下,编译器可以忽略传输1和2——对象v
直接在a
的位置构建,不需要传输。在本例中,编译器必须使用Transfer 1的命名返回值优化,因为返回的对象是命名的。然而,如果我们禁用复制/移动省略,则每次传输都涉及到对a的复制构造函数或其移动构造函数的调用。在大多数现代编译器中,编译器会看到v
即将被销毁,并首先将其移动到返回值中。然后,这个临时返回值将被移动到a
中。如果A
没有移动构造函数,那么它将被复制用于两次传输。
现在我们来看一下:
A compute(A&& v)
{
return v;
}
我们返回的值来自传递给函数的引用。编译器并不只是假设v
是一个临时的,并且可以从它转移到1。在这种情况下,传输1将是一个副本。然后Transfer 2将是一个移动-这没关系,因为返回的值仍然是临时的(我们没有返回引用)。但是,由于我们知道我们已经获取了一个可以从中移动的对象,因为我们的参数是右值引用,所以我们可以明确地告诉编译器将v
视为具有std::move
:的临时对象
A compute(A&& v)
{
return std::move(v);
}
现在,转移1和转移2都将被移动。
1编译器不自动将定义为A&&
的v
视为右值的原因是安全的。弄清楚它不仅太愚蠢。一旦对象有了名称,就可以在整个代码中多次引用它。考虑:
A compute(A&& a)
{
doSomething(a);
doSomethingElse(a);
}
如果a
被自动处理为右值,那么doSomething
将可以自由地将其内脏撕裂,这意味着传递给doSomethingElse
的a
可能是无效的。即使doSomething
按值取其参数,对象也会从中移出,因此在下一行中无效。为了避免这个问题,命名的右值引用是左值。这意味着当调用doSomething
时,a
最坏情况下将从中复制,如果不是仅由左值引用获取的话,它在下一行中仍然有效。
这取决于compute
的作者说,"好吧,现在我允许从中移动这个值,因为我确信它是一个临时对象"。你可以说std::move(a)
。例如,您可以给doSomething
一个副本,然后允许doSomethingElse
从中移动:
A compute(A&& a)
{
doSomething(a);
doSomethingElse(std::move(a));
}
函数结果的隐式移动仅适用于自动对象。右值引用参数并不表示自动对象,因此在这种情况下必须显式请求移动。
第一个利用NVRO,它甚至比移动更好<没有副本比便宜的好。>
第二个不能利用NVRO。假设没有省略,return temp;
将调用复制构造函数,return std::move(temp);
将调用移动构造函数。现在,我相信这两种都有同样的潜力被消除,所以如果没有消除,你应该选择更便宜的,那就是使用std::move
。
在C++17及更高版本中
C++17更改了值类别的定义,使您描述的复制省略类型为保证的(请参阅:保证的复制省略是如何工作的?)。因此,如果您用C++17或更高版本编写代码,那么在这种情况下,您绝对不应该通过std::move()
'ing返回。
- 瓦尔格林德:数学函数"Conditional jump or move depends on uninitialised value(s)"
- Usages of std::move
- 在C++中对T*类型执行std::move的意外行为
- 关于std::move的使用,是否有编译警告
- 我应该实现右值推送功能吗?我应该使用std::move吗
- 通过实例理解std::move及其目的
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 是否可以在 C++03 中定义'move-and-swap idiom'等效项
- 返回一个带有 std::move 的对象并链接函数
- '[](std::list& list)<int>{return std::move(list)}(list)' 是否保证将 'list' 留空?
- 为什么字符串的 move() 会改变内存中底层数据的位置?
- 当 std::move 与 C 样式数组或不移动对象时会发生什么
- 为什么当我为 for(auto& it : myUnorderedMap) {... = std::move(it.second)} 时,我会得到一个 const 引用?
- 添加自定义析构函数时,Move 构造函数在派生类中消失
- 将向量从 N1 缩小到 N2 项,而不触发默认构造函数并仅使用 move 语义
- CPP 中的瓦尔格林德和记忆泄漏:"Conditional jump or move depends on uninitialised values"
- std::move a const std::vector in a lambda capture
- "std::forward"和"std::move"真的不生成代码吗?
- 警告在 std::move'ing 时调用复制 ctor
- 我应该返回一个右值引用(通过 std::move'ing)吗?