3

I have been looking into this article about NRVO.

  class RVO
  {
    public:
    RVO(){
          printf("I am in constructor\n"); }
    RVO(const RVO& c_RVO) { 
          printf("I am in copy constructor\n"); }
    ~RVO(){
          printf("I am in destructor\n"); }
    int mem_var;
  };
  RVO MyMethod(int i)
  {
     RVO rvo;
     rvo.mem_var = i;
     return (rvo);
  }
  int main()
  {
        RVO rvo;
        rvo=MyMethod(5);
  }

The output is the following on visual studio and this is how i understand it

 I am in constructor       // main rvo construction
 I am in constructor       //MyMethod rvo construction 
 I am in copy constructor  //temporary created inside MyMethod
 I am in destructor        //Destroying rvo in MyMethod
 I am in destructor        //Destroying temporary in MyMethod
 I am in destructor        //Destroying rvo of main

If instead i write the main as

 int main()
 { 
    RVO rvo = MyMethod(5);
    return 0;
 }

The output is the following and how understand it

 I am in constructor       //MyMethod rvo construction 
 I am in copy constructor  //temporary created inside MyMethod
 I am in destructor        //Destroying rvo in MyMethod
 I am in destructor        //Destroying rvo of main

Why is temporary not destroyed in Mymethod in the second version?

Why is copy constructor not called in RVO rvo = MyMethod(5);.I think copy constructor should be called twice in second version, one for the temporary created inside Mymethod and the other for RVO rvo = MyMethod(5); I know some call may be getting elided.Can someone please help in explaining these calls.

EDIT: Using return rvo instead of return (rvo) changes the output as

First case

 I am in constructor
 I am in constructor
 I am in destructor
 I am in destructor

second case

 I am in constructor
 I am in destructor       

I guess when i removed the parenthesis, then NRVO kicks in.But i am more interested in the first output when there is no optimization

Community
  • 1
  • 1
Gaurav Sehgal
  • 7,422
  • 2
  • 18
  • 34
  • There's something fishy about the second example's output. Any resonable compiler should apply RVO so you should not get a copy constructor call. – Cheers and hth. - Alf Jun 24 '16 at 05:27
  • @Cheersandhth.-Alf see this http://ideone.com/S5Kqn9 – Gaurav Sehgal Jun 24 '16 at 05:28
  • Remove the parenthesis around the return expression. – Benjamin Lindley Jun 24 '16 at 05:33
  • @GauravSehgal: Right, and g++ doesn't optimize. But Visual C++ applies RVO, as it should. – Cheers and hth. - Alf Jun 24 '16 at 05:35
  • Get rid of the brackets in the return http://ideone.com/vfHN5W – Chris Drew Jun 24 '16 at 05:37
  • @ChrisDrew: Thanks! It doesn't make sense, though. I wonder if it's been reported as a bug? – Cheers and hth. - Alf Jun 24 '16 at 05:39
  • @BenjaminLindley: Thanks to you also! Sorry I didn't see it first. Looks like a compiler bug to me; the parenthesis doesn't make the expression an rvalue. – Cheers and hth. - Alf Jun 24 '16 at 05:41
  • @Cheersandhth.-Alf: I'm not sure it's a bug. The relevant standard text (12.8/31) concerning NRVO says: *"when the expression is the name of a non-volatile automatic object"* -- If you put parens around the name, it is more than just the name. – Benjamin Lindley Jun 24 '16 at 05:51
  • @BenjaminLindley: Unless the parenthesis is documented as a device to turn off optimization (which I'd say has a 0.00001 probability at best) it's a bug, because the parenthesis doesn't change the semantics of the source code. It doesn't change the effect. This compiler optimizes when the effect is expressed in one way, and not when the exact same effect is expressed in another way. – Cheers and hth. - Alf Jun 24 '16 at 07:07

2 Answers2

0

The first descructor call is from the desctruction of the rvo in main. The object first created has to be deleted. It is not copy assignment which is done, it is copy construction.

thorsan
  • 1,034
  • 8
  • 19
0

UPDATE: addressing the output of the updated program, using return rvo rather than return (rvo);

I am in constructor
I am in constructor
I am in destructor
I am in destructor

The reason you see this is that both objects (MyMethod::rvo and main::rvo) undergo default construction, then the latter is assigned to as a separate action but you're not logging that.


You can get a much better sense of what is going on by outputting the addresses of the objects, and the this pointer values as functions are called:

#include <cstdio>
#include <iostream>
class RVO
  {
    public:
    RVO(){
          printf("%p constructor\n", this); }
    RVO(const RVO& c_RVO) {
          printf("%p copy constructor, rhs %p\n", this, &c_RVO); }
    ~RVO(){
          printf("%p destructor\n", this); }
    int mem_var;
  };
  RVO MyMethod(int i)
  {
     RVO rvo;
     std::cout << "MyMethod::rvo @ " << &rvo << '\n';
     rvo.mem_var = i;
     return (rvo);
  }
  int main()
  {
        RVO rvo=MyMethod(5);
        std::cout << "main::rvo @ " << &rvo << '\n';
  }

The output will also depend on whether you compile with optimisations; you link to Microsoft documentation, so perhaps you're using the Microsoft compiler - try cl /O2.

Why is temporary not destroyed in Mymethod in the second version?

There was no temporary there - the object in main was directly copy-constructed. Stepping you through it:

002AFA4C constructor
MyMethod::rvo @ 002AFA4C   // MyMethod::rvo's constructed

002AFA70 copy constructor, rhs 002AFA4C   // above is copied to 2AFA70
002AFA4C destructor        // MyMethod::rvo's destructed
main::rvo @ 002AFA70       // turns out the copy above was directly to main::rvo
002AFA70 destructor        // main::rvo's destruction

[Alf's comment below] "directly copy-constructed" is not entirely meaningful to me. I think the OP means the rvo local variable

Consider the enhanced output from the first version of the program (without optimisation):

002FF890 constructor  // we find out this is main::rvo below
002FF864 constructor  // this one's MyMethod::rvo
MyMethod::rvo @ 002FF864
002FF888 copy constructor, rhs 002FF864  // 2FF888 is some temporary
002FF864 destructor   // there goes MyMethod::rvo
002FF888 destructor   // there goes the temporary
main::rvo @ 002FF890
002FF890 destructor   // and finally main::rvo

If we tie that back in to the OP's output and annotations...

I am in constructor       // main rvo construction
I am in constructor       //MyMethod rvo construction 
I am in copy constructor  //temporary created inside MyMethod
I am in destructor        //Destroying rvo in MyMethod
I am in destructor        //Destroying temporary in MyMethod
I am in destructor        //Destroying rvo of main

The OP (correctly) refers to the copy-constructed object as a temporary. When I say of the second version of the program "There was no temporary there - the object in main was directly copy-constructed." - I mean that there's no temporary equivalent to that in the first program we analysed directly above, and instead it's main::rvo that's copy-constructed from MyMethod::rvo.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • "directly copy-constructed" is not entirely meaningful to me. I think the OP means the `rvo` local variable. – Cheers and hth. - Alf Jun 24 '16 at 05:36
  • 1
    @Cheersandhth.-Alf: I step through it in the latest edit - hope that's clear. Cheers. – Tony Delroy Jun 24 '16 at 05:46
  • @TonyD The output for the second case that you have mentioned shows that there was some optimization done. right?I ran the second case with optimization off, even then i get the same output(with some optimization).If the optimization is off, there should be two copy constructor calls.Right? – Gaurav Sehgal Jun 24 '16 at 05:58
  • @GauravSehgal: all the output I show above is without any optimisation flags specified during compilation, and with the question's original `return (rvo)` code that inhibited RVO in such unoptimised compiles. I've rechecked and never see two copy constructor calls - why do you think there should be two? – Tony Delroy Jun 24 '16 at 06:08
  • @GauravSehgal: Most compilers try not to change the observable behavior of correct programs based on optimization levels. So all applicable forms of copy elision are performed regardless of optimization level. Some compilers have an option to explicitly turn it off though. I believe it's `-fno-elide-constructors` for gcc (and maybe clang too). – Benjamin Lindley Jun 24 '16 at 06:12
  • @TonyD `RVO rvo = MyMethod(5);` Irrespective of how MyMethod is implemented, should'nt this call copy constructor, like `RVO rvo = rvo2` would call.And the other one would be inside `MyMethod` when a temporary is made.I am considering no optimization – Gaurav Sehgal Jun 24 '16 at 06:12
  • @TonyD So `RVO rvo = MyMethod(5);` will call the copy constructor irrespective of optimization and the copy constructor call inside `MyMethod` (creating a temporary) will depend on optimization.Am i right on this. – Gaurav Sehgal Jun 24 '16 at 06:26
  • @GauravSehgal: (sorry - either you edited your comment whuile I was replying or I got myself confused: trying again:) `RVO rvo = MyMethod(5);` - in the C++ Standard the `=` form of a *brace-or-equal-initializer* value causes *copy-initialization*, which boils down to seeking a suitable constructor from the right-hand-side value: in this case copy-construction is implied, but with RVO added into the mix it may be copy-construction direct from `MyMethod::rvo` instead of any additional temporary. – Tony Delroy Jun 24 '16 at 06:26
  • @TonyD If RVO is not there, then can we expect two copy constructor calls?(one copy initialization and other creating a temporary inside `MyMethod`) – Gaurav Sehgal Jun 24 '16 at 06:31
  • *"`RVO rvo = MyMethod(5);` will call the copy constructor irrespective of optimization"* - correct. *"...and the copy constructor call inside `MyMethod` (creating a temporary) will depend on optimization."* - the unnamed temporary was only ever created in the version of the program that default-constructed `main::rvo` then did normal assignment on it. In other words, for whatever reason the compiler only generated a temporary for the `RVO rvo; rvo = MyMethod(5);` version - so you never get two copy construction invocations (at least with MSVC2013/VC12, per my output). – Tony Delroy Jun 24 '16 at 06:33
  • @TonyD I get it.Thanks a lot for your help. – Gaurav Sehgal Jun 24 '16 at 06:35
  • Sure - no worries. It's weird compiler behaviour, having the temporary seemingly arbitrarily thrown into the mix when RVO's not used. Cheers. – Tony Delroy Jun 24 '16 at 06:36