seemuch
seemuch

Reputation: 643

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.

Upvotes: 10

Views: 1117

Answers (2)

galop1n
galop1n

Reputation: 8824

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

Upvotes: 3

marcinj
marcinj

Reputation: 49976

Looks like you get there a lot of copies, with g++4.8 I get following output:

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

if I add: -fno-elide-constructors then I get :

Constructor: 0x7fff1bd329b0   for this line: ArrayWrapper a(7);
Temp object created!
Move constructor: 0x7fff1bd329f0  for temporary
Move from: 0x7fff1bd329b0  moving from this : ArrayWrapper a(7)
Destructor: 0x7fff1bd329b0   foo is ending so destroy: ArrayWrapper a(7);
Move constructor: 0x7fff1bd329e0  for ArrayWrapper b - it is being created
Move from: 0x7fff1bd329f0  moving from temporary
Destructor: 0x7fff1bd329f0 destroying temporary
Finish!
Destructor: 0x7fff1bd329e0  destroy object b

http://coliru.stacked-crooked.com/a/377959ae1e93cdc9

Upvotes: 0

Related Questions