Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Named Return Value Optimization with nested function calls

Tags:

c++

c++11

I know that NRVO allows a function to construct an object and return that object by value without the cost of a copy or even move operation. It found that it also works with nested function calls, allowing you to construct the object from the return value of another function call.

Please consider the following program and it's output as shown in the comments:
(Output from Visual Studio 2017, version 15.2, release build.)

#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()
}

TestM is the normal NRVO case. The object W is constructed and destructed only once. TestTM is the nested NRVO case. Again the object is constructed only once and never copied or moved. So far so good.

Now getting to my question - how can I make TestT1 work with the same efficiency as TestTM? As you can see in TestT1 a second object is move constructed - this is something I would like to avoid. How can I change the function transform1() to avoid any additional copies or moves? If you think about it, TestT1 is not that much different from TestTM, so I have a feeling that this is something that must be possible.

For my second attempt, TestT2, I tried passing the object via RValue reference. This eliminated the extra move constructor, but unfortunately this causes the destructor to be called before I am done with the object, which is not always ideal.

Update:
I also note that it is possible to make it work using references, as long as you make sure not to use the object beyond the end of the statement:

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()

Is this safe to do?

like image 873
Barnett Avatar asked Jun 10 '17 17:06

Barnett


People also ask

Does C have return value optimization?

> Note also that C doesn't have return-value-optimization, hence all your struct-returning functions will cause a call to memcpy (won't happen when compiled in C++ mode of course).

What is named return value optimization?

(Named) Return value optimization is a common form of copy elision. It refers to the situation where an object returned by value from a method has its copy elided. The example set forth in the standard illustrates named return value optimization, since the object is named.

Is NRVO guaranteed?

Compilers often perform Named Return Value Optimization (NRVO) in such cases, but it is not guaranteed.

What is copy move elision?

C++ Copy Elision Purpose of copy elision Copy elision (sometimes called return value optimization) is an optimization whereby, under certain specific circumstances, a compiler is permitted to avoid the copy or move even though the standard says that it must happen.


1 Answers

This happens in Test1 because the compiler is explicitly disallowed to apply NRVO from by value parameters from a function's argument list. And in Test1 you are accepting a W instance by value as a function parameter, so the compiler cannot elide the move on return.

See Why are by-value parameters excluded from NRVO? and my discussion with Howard Hinnant about the issue here Why does for_each return function by move in the comments

You cannot make Test1 work as efficiently as you did in the earlier case because of this.


The relevant quote from the standard

15.8.3 Copy/move elision [class.copy.elision]

  1. When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, ...

    • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function parameter or a variable introduced by the exception-declaration of a handler (18.3)) with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function call’s return object
like image 190
Curious Avatar answered Sep 20 '22 18:09

Curious