Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copy constructor is not called when return by value

I was playing around with C++ constructors. Here is my code:

#include <iostream>
using namespace std;

class ArrayWrapper
{
public:
    // default constructor produces a moderately sized array
    ArrayWrapper ()
        : _p_vals( new int[ 64 ] )
        , _size( 64 )
    {
        cout << "Default constructor: " << this << endl;
    }

    explicit ArrayWrapper (int n)
        : _p_vals( new int[ n ] )
        , _size( n )
    {
        cout << "Constructor: " << this << endl;
    }

    // move constructor
    ArrayWrapper (ArrayWrapper&& other)
        : _p_vals( other._p_vals  )
        , _size( other._size )
    {
            cout << "Move constructor: " << this << endl;
            cout << "Move from: " << &other << endl;
            other._p_vals = NULL;
            other._size = 0;
    }

    // copy constructor
    ArrayWrapper (const ArrayWrapper& other)
        : _p_vals( new int[ other._size  ] )
        , _size( other._size )
    {
            cout << "Copy constructor: " << this << endl;
            for ( int i = 0; i < _size; ++i )
            {
                    _p_vals[ i ] = other._p_vals[ i ];
            }
    }
    ~ArrayWrapper ()
    {
            cout << "Destructor: " << this << endl;
            delete [] _p_vals;
    }

public:
    int *_p_vals;
    int _size;
};

ArrayWrapper foo() {
    ArrayWrapper a(7);
    cout << "Temp object created!" << endl;
    return a;
}


int main() {
    ArrayWrapper b(foo());
    cout << "Finish!" << endl;
}

The output is:

Constructor: 0x7fff5d97bb60
Temp object created!
Destructor: 0x7fff5d97bb60
Move constructor: 0x7fff5d97bbd0
Move from: 0x7fff5d97bbc0
Destructor: 0x7fff5d97bbc0
Finish!
Destructor: 0x7fff5d97bbd0

The first three line indicates that the local variable in foo() function is created with constructor, and destroyed when foo() returns. The 4th line indicates that b is constructed using move constructor. But, the next two lines are most confusing: I now have a new address, that is different from the local variable "a" in foo(), that I used to call the move constructor. When the copy constructor finishes, the rvalue reference vanishes, and destructor is called. But why isn't there a copy constructor for 0x7fff5d97bbc0? In other words, where does 0x7fff5d97bbc0 come from and how is it constructed? It is simply wired that there is one more destructors called than constructors called.

I got a feeling that this has something todo with copy elision. Thus I changed the return line in foo() to the following:

return std::move(a);

And the output is:

Constructor: 0x7fff55a7ab58
Temp object created!
Copy constructor: 0x7fff55a7abc0
Destructor: 0x7fff55a7ab58
Move constructor: 0x7fff55a7abd0
Move from: 0x7fff55a7abc0
Destructor: 0x7fff55a7abc0
Finish!
Destructor: 0x7fff55a7abd0

Now it finally made some sense: on the third line, it shows that copy constructor is called before "a" is destroyed. This means, when returning by value, it actually copied the value into the return value before destroy the temporary variable.

But I still got confused by the original program (without std::move()), because if it is really caused by copy elision, shouldn't foo()'s return value's address be the same with the local variable "a"? Now that it is different, which means it locates in a total different position in the memory from "a", then why didn't it call the copy constructor?

Hope my question is clear and understandable.

-------------------------------------------------------------------------------

Edit: the compiler I used was clang++ with -fno-elide-constructors flag.

like image 851
seemuch Avatar asked Jan 30 '14 21:01

seemuch


1 Answers

What is your compiler, clang without the std::move:

Constructor: 0x7fff0b8e3b80
Temp object created!
Finish!
Destructor: 0x7fff0b8e3b80

with the std::move:

Constructor: 0x7fffca87eef0
Temp object created!
Move constructor: 0x7fffca87ef30
Move from: 0x7fffca87eef0
Destructor: 0x7fffca87eef0
Finish!
Destructor: 0x7fffca87ef30

This two results are far more logical than yours, so again, what is your compiler ?

Edit : It tastes like a bug with the -fno-elide-constructors flag.

by adding an int member after the two original members, same result, but if the int is first, memory corruption ! And the non corruption version ends with a nullptr value in the main ArrayWrapper. See the " and delete " log to catch the erroneous behavior.

http://coliru.stacked-crooked.com/a/f388c504b442b71d <- int after, ok

http://coliru.stacked-crooked.com/a/9beced1d5a2aa6e4 <- int before, corruption dump

like image 150
galop1n Avatar answered Sep 27 '22 19:09

galop1n