Stack Overflow is a community of 4.7 million programmers, just like you, helping each other.

Join them; it only takes a minute:

Sign up
Join the Stack Overflow community to:
  1. Ask programming questions
  2. Answer and help your peers
  3. Get recognized for your expertise

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 ### ###

share|improve this question
5  
1) the operand of the return statement is moved into the return value, 2) the return value is moved into myObject. – Kerrek SB yesterday
    
The other move is from f() into myObject, because copy-elision is disabled. – kennytm yesterday

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

share|improve this answer
    
Will you tell me more about the point n.1? Move the temporary into the return value. What's the difference between return Foo("hello") and Foo obj("hello") return obj – gedamial yesterday
    
@gedamial The first one will move the temporary Foo into the return value because Foo("Hello") is an rvalue, the second one will copy obj into the return value because obj is an lvalue. (Only if copy elision is disabled, of course) – TartanLlama yesterday
    
Meanwhile return {"Hello"}; directly moves the return value into myObject? All these rules are tough to remember. Is there any documentation? – gedamial yesterday
1  
@gedamial Kind of; return {"Hello"}; directly initializes the return value, which is then moved into myObject. You can have a look at the cppreference documentation for return statements. – TartanLlama yesterday
1  
It is an lvalue, but it's first considered as an rvalue because it's in a copy elision context, even though you disabled copy elision. There's a note about this rule in the documentation I linked to. – TartanLlama yesterday

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 ]

share|improve this answer
    
Consider this example: prntscr.com/bbgvzw "then gets moved into the return value placeholder", since it's a LValue, why is it moved and not copied? – gedamial yesterday
    
It is in fact an rvalue. If you had created it separately like this: Foo local("hello"); return local; then it would be an lvalue – Smeeheey yesterday
1  
See update in answer – Smeeheey yesterday
1  
From f() to its return value placeholder. As it happens this is ignored in main but the compiler can't know that. The code to move is generated in f(), which may be called from other places that don't ignore the returned value. – Smeeheey yesterday
1  
thanks to you guys, I've now learnt something strange: every function has a return placeholder to be filled before being sent outside the function. This sounds very strange and complex, is there any documentation/guideline about this? (except en.cppreference.com/w/cpp/language/return) – gedamial yesterday

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.