14

I'm new to C++0x and I'm trying to wrap my head around rvalue references and move constructors. I'm using g++ 4.4.6 with -std=c++0x, and I'm confused by the following piece of code:

 class Foo { public: Foo() : p( new int(0) ) { printf("default ctor\n"); } Foo( int i ) : p( new int(i) ) { printf("int ctor\n"); } ~Foo() { delete p; printf("destructor\n"); } Foo( const Foo& other ) : p( new int( other.value() ) ) { printf("copy ctor\n"); } Foo( Foo&& other ) : p( other.p ) { printf("move ctor\n"); other.p = NULL; } int value() const { return *p; } private: // make sure these don't get called by mistake Foo& operator=( const Foo& ); Foo& operator=( Foo&& ); int* p; }; Foo make_foo(int i) { // create two local objects and conditionally return one or the other // to prevent RVO Foo tmp1(i); Foo tmp2(i); // With std::move, it does indeed use the move constructor // return i ? std::move(tmp1) : std::move(tmp2); return i ? tmp1 : tmp2; } int main(void) { Foo f = make_foo( 3 ); printf("f.i is %d\n", f.value()); return 0; } 

I find that as written, the compiler uses the copy constructor build the object in main(). When I use the std::move line inside make_foo(), then the move constructor is used in main(). Why is std::move necessary inside make_foo()? I would think that although tmp1 and tmp2 are named objects inside make_foo(), when they're returned from a function they should become temporaries.

4
  • In "return i ? tmp1 : tmp2;" are not rvalues. So the compiler can't automatically decide to "move" the values out of the objects as they could be used later/elsewhere but would have had their contents destroyed (even though they are not in your example). So you have to manually make them rvalues using the std::move function to say yes I'm happy for you to move the value out of these objects. At least that's my understanding of how it works. Commented Nov 7, 2012 at 16:01
  • 1
    Removing the copy constructor gives following error (gcc 4.6.3): cannot bind Foo lvalue to Foo&& showing that the compiler still sees the return value as an lvalue. Commented Nov 7, 2012 at 16:04
  • In C++11 you can use the keyword delete to explicitly delete the default assignment operators, instead of just leving them undefined (Foo& operator=( const Foo& ) = delete;). This way you get a compiler error if you try to use them instead of just a linker error. Commented Nov 7, 2012 at 16:08
  • Making them private would cause a compiler error as well. I agree, though, that "= delete" is clearer. As I said, I'm new to C++11 :-) Commented Nov 7, 2012 at 16:27

1 Answer 1

11

This is your problem:

return i ? tmp1 : tmp2; 

A local variable in a function will only be moved from in the return statement if the return statement is just return var;. If you want to do that test you will need to use an if:

if (i) { return tmp1; } else { return tmp2; } 

The citation is a bit convoluted, but it is in 12.8/31 and 12.8/32

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 [...]

That is even if the expression is an lvalue, it will be considered to be an rvalue when the criteria in 12.8/31 is met, the second option in that block is:

12.8/31 in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value.

Which determines that return tmp; allows for copy elision, but return (cond?tmp:tmp); doesn't.

Note that for the compiler to generate an implicit std::move in the return statement, the returned object must be a candidate for elision unless the it is also an argument to the function. Using the conditional operation inhibits copy elision, and at the same time inhibits the compiler from doing moving out of your objects. That second case might be simpler to code:

Foo make_foo(Foo src) { return src; // Copy cannot be elided as 'src' is an argument } 
Sign up to request clarification or add additional context in comments.

11 Comments

Do you have a citation? I was researching this, and I could only find people using the above style example as a way to avoid RVO and cause an implicit move to occur. I have not found the verbage in the standard that makes it clear when the compiler can implicitly move. (On the other hand, I believe that implicit move is always optional, so the compiler can say that they only do it when you do return foo; directly, or only do it at non-trivial optimization levels, etc)
Could this be summarised simply as "the expression i ? tmp1 : tmp2 is not a named variable"?
The text that allows moving out of an lvalue in a return statement is 12.8/32, and is based on the conditions fro copy elision determined in 12.8/31. If you inhibit copy-elision, you also inhibit the implicit move.
Thanks. If I replace the ?: with an if, it does indeed do what I expect. I would be interested in the citation, if you have it. I haven't seen anywhere else a specification of the rule about when local variables can be implicitly moved in a return.
@LightnessRacesinOrbit: That begs the question: Why does the fact that it is not a named variable matter?
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.