10
votes

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.

2
Can you please post the compiler in the question? It seems like nobody can exactly reproduce your results. I've also tried vc++ on Rextesteruser1508519
The point of the move constructor is that it can move the object members from foo.a to main.b. So no copy is required. Add cout << "main.b located at " << &b << endl; to main() to see this.Hans Passant
I used clang++ with -fno-elide-constructors flag.seemuch
@HansPassant I added the line as you said and got a even wireder output: after everything is output, there is an error msg: a.out(17285,0x7fff7a537310) malloc: *** error for object 0x7fff5c49bc20: pointer being freed was not allocated *** set a breakpoint in malloc_error_break to debug Abort trap: 6seemuch
@Tomás Badan There is something weirder, i tried by deleting the operator= and there is still a invisible copy in the version with copy elision disabled, the log shows 2 constructors and 3 destructors : coliru.stacked-crooked.com/a/de9c5b3a410a5084galop1n

2 Answers

3
votes

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

0
votes

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