在无法完成RVO的情况下,我们应该写`std :: move`吗?

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&).

Oli*_*liv 6

您的示例中发生的事情与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:

  1. 三元运算符结果推导为prvalue [expr.cond]/5
  2. 然后test2通过lvalue-to-rvalue转换,这将导致[expr.cond]/6中所需的复制初始化
  3. 然后将返回值的移动构造省略[class.copy] /31.3

因此,在您的示例代码中,移动elision发生,然而在形成三元运算符的结果所需的复制初始化之后.