Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is using the move constructor in a return statement legal?

Tags:

c++

c++11

Consider the following:

#include <iostream>

#define trace(name) std::cout << #name << " (" << this << "), i = " << i << std::endl

class C
{
    C(C const&);
    C& operator=(C const&);
public:
    int i;
    C() : i(42) { trace(CTOR); }
    C(C&& other) : i(other.i) { trace(MOVE); other.i = 0; }
    C& operator=(C&& other) { trace(ASGN); other.i = 0; return *this; }
    ~C() { trace(DTOR); }
};

C
func1(bool c)
{
    C local;
    if (c)
        return local;
    else
        return C();
}

int
main()
{
    C local(func1(true));
    return 0;
}

Both MSC and g++ allow the return local, and use the move constructor (as shown by the output) when doing so. While this makes a lot of sense to me, and I think it probably should be the case, I can't find the text in the standard that authorizes it. As far as I can see, the argument to the move constructor must be either a prvalue (which it clearly isn't) or an xvalue; it is in fact an lvalue, which would make the return just as illegal as C other = local; in the function body (which does fail to compile).

like image 714
James Kanze Avatar asked Oct 06 '14 17:10

James Kanze


2 Answers

When move semantics where added to C++ in C++11, decisions where made about where to automatically do move construction.

The general rule that was followed was that implicit move should occur when copy elision would be legal.

Copy elision is legal when you have an anonymous object that you are copying to another instance. The compiler can legally elide the copy, and treat the two objects as one, with a lifetime equal to the union of the two objects lifetime.

The other time that copy elision is legal is when you return a local variable from a function. This was known as NRVO (named return value optimization). This optimization in C++03 let you declare a return value, and it is constructed directly in the place where the return value goes. When you return retval;, no copy happens.

This is basically impossible to do without inlining when you return multiple different objects at different spots in your function.

However, when move semantics where added, this place was the other spot where implicit move can occur. A return local_var; in a function returning a variable the same type as local_var can be elided, and failing that, you can implicitly move from local_var into the return value.

like image 195
Yakk - Adam Nevraumont Avatar answered Sep 27 '22 22:09

Yakk - Adam Nevraumont


C++11 12.8/32: When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

Returning a local automatic variable meets the criteria for elision; treating it as if it were an rvalue selects the move constructor.

like image 28
Mike Seymour Avatar answered Sep 27 '22 23:09

Mike Seymour