Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is move constructor called twice in C++?

Look at this code:

class Foo
{
public:

    string name;

    Foo(string n) : name{n}
    {
        cout << "CTOR (" << name << ")" << endl;
    }

    Foo(Foo&& moved)
    {
        cout << "MOVE CTOR (moving " << moved.name << " into -> " << name << ")" << endl;

        name = moved.name + " ###";
    }

    ~Foo()
    {
        cout << "DTOR of " << name << endl;
    }
};

Foo f()
{
    return Foo("Hello");
}

int main()
{
    Foo myObject = f();

    cout << endl << endl;
    cout << "NOW myObject IS EQUAL TO: " << myObject.name;
    cout << endl << endl;

    return 0;
}

The output is:

[1] CTOR (Hello)

[2] MOVE CTOR (moving Hello into -> )

[3] DTOR of Hello

[4] MOVE CTOR (moving Hello ### into -> )

[5] DTOR of Hello ###

[6] NOW two IS EQUAL TO: Hello ### ###

[7] DTOR of Hello ### ###

Important note: I have disabled the copy elision optimization using -fno-elide-constructors for testing purposes.

The function f() constructs a temporary [1] and returns it calling the move constructor to "move" the resources from that temporary to myObject [2] (additionally, it adds 3 # symbols).

Eventually, the temporary is destructed [3].


I now expect myObject to be fully constructed and its name attribute to be Hello ###.

Instead, the move constructor gets called AGAIN, so I'm left with Hello ### ###

like image 696
gedamial Avatar asked Jun 02 '16 14:06

gedamial


People also ask

Is move constructor automatically generated?

If a copy constructor, copy-assignment operator, move constructor, move-assignment operator, or destructor is explicitly declared, then: No move constructor is automatically generated. No move-assignment operator is automatically generated.

What is the difference between a move constructor and a copy constructor?

If any constructor is being called, it means a new object is being created in memory. So, the only difference between a copy constructor and a move constructor is whether the source object that is passed to the constructor will have its member fields copied or moved into the new object.

How many times copy constructor is called?

And there are 4 calls to copy constructor in f function. 1) u is passed by value. 2) v is copy-initialized from u . 3) w is copy-initialized from v . 4) w is copied on return.

Why is constructor called only once?

By definition an object is only constructed once, hence the constructor is only called once.


2 Answers

The two move constructor calls are:

  1. Move the temporary created by Foo("Hello") into the return value.
  2. Move the temporary returned by the f() call into myObject.

If you used a braced-init-list to construct the return value, there would only be a single move construction:

Foo f()
{
    return {"Hello"};
}

This outputs:

CTOR (Hello)
MOVE CTOR (moving Hello into -> )
DTOR of Hello    
NOW myObject IS EQUAL TO: Hello ###    
DTOR of Hello ###

Live Demo

like image 172
TartanLlama Avatar answered Oct 12 '22 01:10

TartanLlama


Because you turned off copy elision, your object first gets created in f(), then gets moved into the return value placeholder for f(). At this point f's local copy is destroyed. Next the return object is moved into myObject, and also destroyed. Finally myObject is destroyed.

If you didn't disable copy elision, you would have seen the sequence you expected.

UPDATE: to address question in comment below, which is - given the definition of a function like this:

Foo f()
{
    Foo localObject("Hello");
    return localObject;
}

Why is the move constructor invoked in the creation of the return-value object with copy elision disabled? After all, localObject above is an lvalue.

The answer is that the compiler is obliged in these circumstances to treat the local object as an rvalue, so effectively it is implicitly generating the code return std::move(localObject). The rule that requires it to do so is in the standard [class.copy/32] (relevant parts highlighted):

When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

...

[ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. — end note ]

like image 22
Smeeheey Avatar answered Oct 12 '22 02:10

Smeeheey