我不知道何时应使用
std::move
以及何时应让编译器进行优化……例如: #1 楼
仅使用第一种方法:Foo f()
{
Foo result;
mangle(result);
return result;
}
如果有可用的话,这将已经允许使用move构造函数。实际上,在允许复制省略时,局部变量可以精确地绑定到
return
语句中的右值引用。您的第二个版本主动禁止复制省略。第一个版本普遍更好。
评论
即使禁用复制省略(-fno-elide-constructors),也将调用move构造函数。
–马吉耶罗
4月21日0:06
@Maggyero:-fno-elide-constructors不会禁用复制省略,它会禁用返回值优化。前者是您不能“禁用”的语言规则。后者是利用此规则的优化。确实,我的意思是,即使不使用返回值优化,您仍然可以使用移动语义,这是同一套语言规则的一部分。
– Kerrek SB
4月21日11:33
GCC有关-fno-elide-constructors的文档:“ C ++标准允许实现省略创建仅用于初始化另一个相同类型对象的临时文件。指定此选项将禁用该优化,并强制G ++调用复制构造函数在所有情况下,该选项都会导致G ++调用琐碎的成员函数,否则将对其进行内联扩展。在C ++ 17中,要求编译器忽略这些临时变量,但是此选项仍然会影响琐碎的成员函数。
–马吉耶罗
4月21日13:03
@Maggyero:听起来像是文档中的错误,尤其是听起来文档的措辞并未针对C ++ 11更新。提交错误? @JonathanWakely?
– Kerrek SB
4月21日14:08
在C ++ 17(C ++ 11和C ++ 14)之前,-fno-elide-constructors编译选项禁用所有复制要素,即用于返回语句glvalue / prvalue对象的初始化程序(这些复制要素分别称为NRVO / RVO),可变prvalue对象。初始化程序,引发表达式glvalue对象初始化程序和catch子句glvalue对象初始化程序。从C ++ 17开始,对于返回语句prvalue对象初始化程序和变量prvalue对象初始化程序,复制省略是必需的,因此该选项现在仅在其余情况下禁用复制省略。
–马吉耶罗
4月21日14:40
#2 楼
所有返回值都已经moved
或已优化,因此无需显式移动返回值。允许编译器自动移动返回值(以优化副本),甚至
n3337标准草案(C ++ 11)第12.8节:
当满足某些条件时,允许省略实现
类对象的复制/移动构造,即使该对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下,实现将
省略的复制/移动操作的源和目标当作引用
相同对象的两种不同方式,并且该对象的破坏发生在
后来在没有优化的情况下销毁了两个对象
。在以下情况下,允许复制/移动操作的这种省略,称为复制省略,
(可以合并以消除多个副本):
[...]
示例:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
Thing t;
return t;
}
Thing t2 = f();
这里的省略标准可以组合起来,以消除对
Thing
类的复制构造函数的两次调用:将该临时对象复制到对象t
中。实际上,可以将本地对象f()
的构造视为直接初始化全局对象
t2
,并且该对象的破坏将发生在程序出口处。向
t
添加move构造函数具有相同的效果,但是从临时对象到
t2
的move构造被忽略了。 —结束示例] 当满足或将要满足复制操作的省略标准时,除了源
object是一个函数参数,并且要复制的对象由lvalue指定,首先执行重载决议以选择复制的构造函数,就好像该对象由rvalue指定一样。如果重载
解析失败,或者所选构造函数的第一个参数的类型不是对对象类型的右值引用(可能是cv限定),则考虑对象,再次执行重载解析作为
左值。
评论
我并不特别喜欢整个“编译器可以执行X”的论点。这个问题不需要求助于任何编译器。纯粹是关于语言。而且,是否有任何“举动”没有“选择”或含糊。该语言非常清楚,哪种类型的构造函数参数可以绑定到返回值(它是一个xvalue)。重载解析将完成其余的工作。
– Kerrek SB
13年7月4日在18:08
这与编译器的功能无关,而是主要编译器的功能。显式地移动事物可能会比编译器做事更好。任何可以允许您显式移动的高级编译器几乎都可以肯定可以自动移动返回值的高级-因为与其他可能需要显式移动的情况不同,返回值很容易被编译器检测为好地方优化(因为任何返回值都可以保证该值不会在执行返回的函数中进一步使用)。
–詹敏·格雷(Jamin Gray)
13年7月4日在22:22
@Damon:嗯,有点。它的编译器可以移动返回值(并保存一个副本),但通常不这样做。相反,他们尽可能使用copy-ellison,这样可以保存副本和移动。它们只是直接分配给接收函数结果的变量,而不是返回并随后分配的临时变量。手动移动变量永远不会比编译器的性能好,并且通常会稍差一点。编译器依靠移动语义,但是在可能的情况下宁愿使用RVO。至少,这是我的理解。
–詹敏·格雷(Jamin Gray)
13年11月14日在1:07
“所有返回值已经移出或进行了优化”如果类型不匹配,则不会:groups.google.com/a/isocpp.org/forum/#!msg/std-proposals/…
–cdyson37
2015年5月5日12:24
@ cdyson37有趣的是,我之前从未遇到过这种情况。幸运的是,没有std :: move()它甚至无法编译。我试图弄清楚该示例实际上是在演示预期的语言功能的一部分,还是利用了模板成员函数(在本例中为std :: unique_ptr()的模板移动构造函数)的意外怪癖。
–詹敏·格雷(Jamin Gray)
15年5月7日在17:28
#3 楼
这很简单。return buffer;
如果执行此操作,则将发生NRVO或不会发生。如果没有发生,那么
buffer
将被移出。 /> 因此在这里使用
return std::move( buffer );
并不会带来任何收益,而且会造成很多损失。 > 如果
buffer
是右值引用,则应使用std::move
。这是因为引用不符合NRVO的条件,因此如果没有buffer
,它将导致从左值复制。该规则优先于“从不std::move
返回值”规则。评论
非常重要的例外,谢谢。刚在我的代码中碰到过这一点。
–米哈伊尔
18年3月31日在17:08
对于一种编程语言来说,这是一种多么有趣的状态,在这种状态下,必须使用内存助记符来对决策树进行编码,以决定如何执行简单的事情,例如返回没有副本的值。作为cpp设计的成功,是否普遍持有移动语义和右值?对于我看来很简单的问题,它们无疑是一个复杂的解决方案。再加上隐式使用NVRO,这无疑会使设计非常混乱。
–ldog
10月29日7:06
#4 楼
如果要返回局部变量,请不要使用move()
。这将允许编译器使用NRVO,否则,仍将允许编译器执行移动(局部变量在return
语句中成为R值)。在这种情况下使用move()
会简单地禁止NRVO并迫使编译器使用移动(如果移动不可用,则使用副本)。如果返回的不是局部变量,则NRVO仍然不是一种选择,并且(且仅)当您打算将对象盗窃时,应使用move()
。评论
那是对的吗?如果我重复使用以下示例:en.cppreference.com/w/cpp/language/copy_elision在return语句上添加std :: move(第17行),不会禁用复制省略。该标准实际上说复制省略将省略“ std :: move”并复制构造函数。
–托马斯·莱格里斯(Thomas Legris)
16-9-5在13:41
@ThomasLegris,我不理解您的评论。如果您正在谈论return v ;,以这种形式,NRVO将取消搬家(和副本)。在C ++ 14中,不需要执行移动省略,但是需要执行复制删除(支持仅移动类型是必需的)。我相信在最新的C ++标准中,也必须取消这一步骤(以支持固定类型)。如果该行返回return std :: move(v);,则不再返回局部变量;您返回的是表达式,而NRVO不符合条件-将需要移动(或复制)。
–亚当·彼得森(Adam H. Peterson)
16-09-20在23:57
看来编译器足够聪明,可以删除std :: move并应用NRVO。添加return std :: move(v);第17行的经验表明,从未调用过移动构造函数和复制构造函数(您可以单击“运行它”并选择编译器选项“ gcc 4.7 C ++ 11”来尝试)。但是Clang输出警告,但仍然可以应用NRVO。因此,我认为不添加std :: move是一个很好的做法,但是添加它并不一定会完全抑制NRVO,这就是我的观点。
–托马斯·莱格里斯(Thomas Legris)
16-09-22在9:55
@ThomasLegris,好吧,我明白了您的所见,但我有其他解释。确实在执行移动,但是移动的是矢量
–亚当·彼得森(Adam H. Peterson)
16-09-26在18:43
@ThomasLegris,就算您感兴趣,在该示例中查看移动操作的另一种方法是将vector
–亚当·彼得森(Adam H. Peterson)
16-09-28在23:21
评论
从我到目前为止所读的内容来看,一般共识似乎是指使用RVO而不是显式移动编译器:现代编译器足够聪明,可以在任何地方使用RVO,并且比移动更有效。请注意,但这只是“传闻”,所以我对记录在案的解释非常感兴趣。您永远不需要显式移动局部变量函数的返回值。这是隐性的动作。
然后,编译器可以自由选择:如果可能,它将使用RVO,否则,它仍然可以执行移动(如果该类型无法移动,则将执行复制)。 >
@MartinBa,永不说永不;)如果局部变量与返回类型的类型不同,则需要显式移动,例如std :: unique_ptr
为了完整起见,@ JonathanWakely所说的已在缺陷报告中得到解决,至少最新版本的gcc和clang不需要在那里进行明确的举动。