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 ### ###
The two move constructor calls are:
Foo("Hello")
into the return value.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 ###