C++使用嵌套函数调用命名返回值优化

Bar*_*ett 6 c++ c++11

我知道NRVO允许函数构造一个对象并按值返回该对象而不需要复制或甚至移动操作的成本.它发现它也适用于嵌套函数调用,允许您从另一个函数调用的返回值构造对象.

请考虑以下程序及其输出,如注释中所示:(
Visual Studio 2017的输出,版本15.2,发布版本.)

#include <stdio.h>
class W
{
public:
  W() { printf( "W::W()\n" ); }
  W( const W& ) { printf( "W::W( const W& )\n" ); }
  W( W&& ) { printf( "W::W( W&& )\n" ); }
  W& operator=( const W& ) { printf( "W::operator=( const W& )\n" ); }
  W& operator=( W&& ) { printf( "W::operator=( W&& )\n" ); }
  ~W() { printf( "W::~W()\n" ); }
  void Transform() { printf( "W::Transform()\n" ); }
  void Run() { printf( "W::Run()\n" ); }
};

W make()
{
  W w;
  return w;
}

W transform_make()
{
  W w{ make() };
  w.Transform();
  return w;
}

W transform1( W w )
{
  w.Transform();
  return w;
}

W&& transform2( W&& w )
{
  w.Transform();
  return std::move(w);
}

int main()                         // Program output:
{
  printf( "TestM:\n" );            //TestM:
  {                                //W::W()
    W w{ make() };                 //W::Run()
    w.Run();                       //W::~W()
  }
                                   //TestTM:
  printf( "TestTM:\n" );           //W::W()
  {                                //W::Transform()
    W w{ transform_make() };       //W::Run()
    w.Run();                       //W::~W()
  }
                                   //TestT1:
  printf( "TestT1:\n" );           //W::W()
  {                                //W::Transform()
    W w{ transform1( make() ) };   //W::W( W&& )
    w.Run();                       //W::~W()
  }                                //W::Run()
                                   //W::~W()

  printf( "TestT2:\n" );           //TestT2:
  {                                //W::W()
    W&& w{ transform2( make() ) }; //W::Transform()
    w.Run();                       //W::~W()
  }                                //W::Run()
}
Run Code Online (Sandbox Code Playgroud)

TestM是正常的NRVO案例.该对象W仅构造和销毁一次. TestTM是嵌套的NRVO案例.同样,对象只构造一次,从不复制或移动.到现在为止还挺好.

现在回答我的问题 - 我怎样才能TestT1以同样的效率开展工作TestTM?你可以在TestT1第二个对象中看到移动构造 - 这是我想要避免的.如何更改功能transform1()以避免任何其他副本或移动?如果你考虑一下,与它TestT1没有多大区别TestTM,所以我觉得这是必须的.

对于我的第二次尝试,TestT2我尝试通过RValue引用传递对象.这消除了额外的移动构造函数,但不幸的是,这导致在完成对象之前调用析构函数,这并不总是理想的.

更新:
我还注意到可以使用引用使其工作,只要您确保不使用语句末尾之外的对象:

W&& transform2( W&& w )
{
  w.Transform();
  return std::move(w);
}

void run( W&& w )
{
  w.Run();
}

printf( "TestT3:\n" );           //TestT3:
{                                //W::W()
  run( transform2( make() ) );   //W::Transform()
}                                //W::Run()
                                 //W::~W()
Run Code Online (Sandbox Code Playgroud)

这样做安全吗?

Cur*_*ous 2

发生这种情况是Test1因为编译器被明确禁止从函数的参数列表中按值参数应用 NRVO。并且在Test1您按值接受W实例作为函数参数,因此编译器无法消除返回时的移动。

\n\n

请参阅为什么按值参数被排除在 NRVO 之外?以及我与 Howard Hinnant 关于此问题的讨论Why does for_each return function by move为什么 for_each 通过在注释中

\n\n

你不能使Test1像之前的情况那样高效地工作。

\n\n
\n\n

标准中的相关引用

\n\n

15.8.3 复制/移动省略 [class.copy.elision]

\n\n
\n
    \n
  1. 当满足某些条件时,允许实现省略类对象的复制/移动构造,...

    \n\n
      \n
    • return具有类返回类型的函数的语句中,当表达式非易失性自动对象的名称时(函数参数或处理程序异常声明引入的变量除外)引入的变量(18.3)除外)时,具有相同的type(忽略cv限定)作为函数返回类型,通过将自动对象直接构造到函数调用\xe2\x80\x99s返回对象中,可以省略复制/移动操作
    • \n
  2. \n
\n
\n