Ash*_*hot 7 c++ return-value move-semantics return-value-optimization copy-elision
众所周知,std::move不应该将其应用于函数返回值,因为它可以阻止RVO(返回值优化).我感兴趣的是,如果我们确实知道RVO不会发生,我们应该怎么做.
这就是C++ 14标准所说的[12.8/32]
当满足复制/移动操作的省略标准时,但不满足异常声明,并且要复制的对象由左值指定,或者当返回语句中的表达式是(可能带有括号的)id-时表达式,用于在最内层封闭函数或lambda-expression的body或parameter-declaration-clause中声明的具有自动存储持续时间的对象,首先执行重载决策以选择复制的构造函数,就像对象由rvalue指定一样.如果第一个重载决策失败或未执行,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv-qualified),则再次执行重载决策,将对象视为左值.[注意:无论是否发生复制省略,都必须执行此两阶段重载决策.如果未执行elision,它将确定要调用的构造函数,并且即使调用被省略,也必须可以访问所选的构造函数. - 结束说明]
这是本书的解释 Effective Modern C++
标准祝福RVO的部分接着说,如果满足RVO的条件,但编译器选择不执行复制省略,则返回的对象必须被视为右值.实际上,标准要求在允许RVO时,发生复制省略或将std :: move隐式应用于返回的本地对象
据我所知,当返回物体最初不能被省略时,它应被视为rvalue.在这些例子中,我们可以看到,当我们传递大于5object的参数时,否则会被复制.std::move当我们知道RVO不会发生时,这是否意味着我们应该明确写出来?
#include <iostream>
#include <string>
struct Test
{
Test() {}
Test(const Test& other)
{
std::cout << "Test(const Test&)" << std::endl;
}
Test(Test&& other)
{
std::cout << "Test(const Test&&)" << std::endl;
}
};
Test foo(int param)
{
Test test1;
Test test2;
return param > 5 ? std::move(test1) : test2;
}
int main()
{
Test res = foo(2);
}
Run Code Online (Sandbox Code Playgroud)
这个程序的输出是Test(const Test&).
您的示例中发生的事情与RVO没有关联,而是与三元组相关联operator ?.如果使用if语句重写示例代码,程序的行为将是预期的行为.将foo定义更改为:
Test foo(int param)
{
Test test1;
Test test2;
if (param > 5)
return std::move(test2);
else
return test1;
}
Run Code Online (Sandbox Code Playgroud)
将输出Test(Test&&).
如果你写的话会发生什么(param>5)?std::move(test1):test2:
test2通过lvalue-to-rvalue转换,这将导致[expr.cond]/6中所需的复制初始化因此,在您的示例代码中,移动elision发生,然而在形成三元运算符的结果所需的复制初始化之后.