简体   繁体   中英

c++ overloaded + operator does not return a temporary object , why?

here is the source code:

#include<iostream>
using namespace std;

class cat{
    private:
      int a;
    public:
      cat():a(1){
         cout << "const : " << this << endl;
      }
     ~cat(){
         cout << "dest :  " << this << endl;
      }
    cat operator+(cat& rhs){
         cout << "+" << endl;
         cat x;
         x.a=a+rhs.a;
         return x;
      }
    cat operator=(const cat& rhs){
        cout << "= :  " <<this << endl;
        a=rhs.a;
        return (*this);
      }
    cat(const cat& rhs){
        cout << "copy const : " << this << endl;
        a=rhs.a;
      }
 };

 int main(){
 cat ob1;
 cat ob2;
 cat ob3;
 ob1=ob2;
 cout << "\n 1----1 \n" << endl;
 ob3=(ob1+ob2);
 cout << "\n 2----2 \n" << endl;
 cat ob4=ob1+ob2;
 cout << "\n 3----3 \n" << endl;

 }

and this is the output:

const : 0x22ff20          // ob1 created
const : 0x22ff1c          // ob2 created
const : 0x22ff18          // ob3 created
= : 0x22ff20             // calling = operator 
copy const  :  0x22ff24   // return temporary object using copy constructor
dest :  0x22ff24          // temporary object is destroyed

  1 ---- 1

+                     // operator + is called
= : 0x22ff2c          // it jums to = operator #### (why ?) ####
copy const : 0x22ff28  // = create a temporary object 
dest :   0x22ff28      // temporary object created by = is destroyed 
dest :   0x22ff2c      // x inside + operator is destroyed

                       // ##################################################
                       // #### HERE #### copy constructor to create a temporory object 
                       // like what happend in = operator and also destructor of this 
                       // temporary object did not called
                       // ##################################################

  2 ---- 2      

+                     // here + operator is called 
const :  0x22ff14     // x is creted 

                      //######################""
                      //#### HERE #### copy constructor ob4 that take ob1+ob2 as an
                      // argument did not get called, why ?
                      // and also + operator did not return a temporary object and then
                      // use it as an argument for the copy constructor
                      //#######################

  3 ---- 3 

dest :  0x22ff14        // x   destroyed
dest :  0x22ff18        // ob3 destroyed
dest :  0x22ff1c        // ob2 destroyed
dest :  0x22ff20        // ob1 destroyed

the problem start between 1 and 2 , and also between 2 and 3.

so my questions are inside the output.
between 1 and 2 : why + operator did not return a teporary object and then destroy as it happend in = operator ?

between 2 and 3 : why + operato did not return a temporary object which will be used as an argument in the copy constructor to create ob4 ?

I know that this is so long, but I really appreciate your help .

The compiler is allowed to remove (elide) copy constructs and build in place (at the final destination). So it is perfectly valid.

PS: Small bug here:

cat operator=(const cat& rhs){

Should be:

cat &   operator=(const cat& rhs){
// ^^^

With this correction I get:

 1----1 

+                       // + called.
const : 0x7fff6b29e848  // Local object to + constructed.
                        // But the return value will be used as a const ref parameter
                        // to the assignment operator. So we can elide the actual copy
                        // if we create the temporary object at the destination and use that.
= :  0x7fff6b29e830     // Now we are in the assignment.
                        // Just copy the value from the temporary object we created as part
                        // of the optimizations.
dest :  0x7fff6b29e848  // All finished destroy the temporary.

                        // Note: I use the term temporary very loosely.
                        //       And refer you to the as-is rule.

So you're issue seems to be this:

In the + method, you create a local cat called 'x', and return it. You're expecting there to be a constructor call for this, followed by a copy constructor creating the anonymous return value, then a destructor for x and finally a destructor for the return value. You want to know why this doesn't happen. (Correct me if I'm wrong)

In short, the answer to this is almost certainly compiler optimization. The compiler sees that you're simply creating x, changing one of its members, and then returning it, and so it takes all that out and simply constructs the return value in place.

The compiler has elided the copy from the internal variable x to the returned value, which is done by placing both objects in the same memory location and extending the lifetime of the object. The lifetime of the temporary extends until the end of the full expression to the point that you have identified as the destruction of x . I don't have a compiler at hand, but I bet that if you print the address of x inside operator+ and the address of the argument to operator= they will be the same.

As of how the optimization is implemented, in all compilers that I know the calling convention for a function that returns by value dictates that the caller reserves the space for the returned value and passes a hidden pointer to the uninitialized memory to the function. The return statement inside the function will create the returned object on that block of memory. In this case, while processing the operator+ the compiler realizes that the whole purpose of x is to serve as the blueprint to construct that return statement and decides to place x in the address passes for the returned value, and avoid having to create two separate objects and copy.

Further read: Value semantics: NRVO // Value Semantics: copy elision

EDIT : Equivalent code transformation of NRVO in operator+

The calling convention makes operator+ equivalent to:

void operator+( void* rtn, cat * const this, cat const &rhs ) {
   cout << "+" << endl;
   cat x;
   x.a=this->a+rhs.a;
   new (rtn) cat(x);    // return x;
} 

Now the compiler sees that it can easily perform NRVO and further transforms this to:

void operator+( void* rtn, cat * const this, cat const &rhs ) {
   cout << "+" << endl;
   new (rtn) cat;                                 // cat x;
   *static_cast<cat*>(rtn).a = this->a+rhs.a;     // `x` is an alias to 
   // return x; unnecessary, the return object has been built in place already
} 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM