8

I use the code below to test copy elision:

class foo { public: foo() {cout<<"ctor"<<endl;}; foo(const foo &rhs) {cout<<"copy ctor"<<endl;} }; int g(foo a) { return 0; } int main() { foo a; g(std::move(a)); return 0; } 

I expected only the default constructor would be called because the argument of g() is an rvalue and copy will be elided. But the result shows that both the default constructor and the copy constructor are called. Why?

And if I change the function call to g(foo()), the copy will be elided. What's the difference between the return types of foo() and std::move(a)? How can I make the compiler elide copy on an lvalue?

3
  • 4
    You can't. g takes its parameter by value so the compiler has to ensure that the object passed is distinct from any object accessible from the calling scope. If the object being passed is an lvalue there is no temporary to eliminate and a copy cannot be elided. Commented Aug 14, 2012 at 6:07
  • 1
    How many destructor calls did you expect? ;) Commented Aug 14, 2012 at 6:29
  • You may want to read up on what std::move actually does. Commented Aug 14, 2012 at 11:25

2 Answers 2

6

Copy elision for can only occur in a few specific situations, the most common of which is the copying of a temporary (the others are returning locals, and throwing/catching exceptions). There is no temporary being produced by your code, so no copy is elided.

The copy constructor is being called because foo does not have a move constructor (move constructors are not implicitly generated for classes with explicit copy constructors), and so std::move(a) matches the foo(const foo &rhs) constructor (which is used to construct the function argument).

A copy of an lvalue can be elided in the following situations (although there is no way to force a compiler to perform the elision):

foo fn() { foo localAutomaticVariable; return localAutomaticVariable; //Copy to construct return value may be elided } int main() { try { foo localVariable; throw localVariable; //The copy to construct the exception may be elided } catch(...) {} } 

If you want to avoid copies when passing function arguments, you can use a move constructor which pilfers the resources of the objects given to it:

class bar { public: bar() {cout<<"ctor"<<endl;}; bar(const bar &rhs) {cout<<"copy ctor"<<endl;} bar(bar &&rhs) {cout<<"move ctor"<<endl;} }; void fn(bar a) { } //Prints: //"ctor" //"move ctor" int main() { bar b; f(std::move(b)); } 

Also, whenever copy elision is allowed but does not occur, the move constructor will be used if it is available.

Sign up to request clarification or add additional context in comments.

1 Comment

+1, it may be worth noting that by default there is no move constructor so the construction of the argument had to fall back to the copy constructor, even as an r-value.
4

You need to declare g as:

int g(foo && a) //accept argument as rvalue reference { return 0; } 

Now it can accept argument by rvalue-reference.

In your case, even though the expression std::move(a) produces rvalue, it doesn't bind to a parameter which accepts argument by value. The receiving end must be rvalue-reference as well.

In case of g(foo()), the copy-elision is performed by the compiler, which is an optimization. It is NOT a requirement by the language[until C++17]. You can disable this optimization if you want to : then g(foo()) and g(std::move(a)) will behave exactly same, as expected.

But if you change g as I suggested above, the call g(foo()) will not make a copy because it is a requirement by the language to not make copy with &&. It is not a compiler-optimization anymore.

7 Comments

I think you're missing the point of the question. There will be no copy elision with that signature, simply because there will be no copying.
@hvd: I didn't understand your comment. What is the basis of this : "There will be no copy elision with that signature, simply because there will be no copying"?
One could argue that copy-ellision is by definition an optimization performed by the compiler (although I am not sure if that is correct). In that case, your solution is not copy-ellision.
Yes, your edit makes sense. I meant pretty much what Björn Pollex said, g(foo()) in your version isn't copy elision, because the semantics of the call don't involve a copy in the first place. With your edit it's now clear to me what you mean and how it relates to the question, even if you worded it differently from how I would have, so +1 from me.
"But if you change g as I suggested above, g(foo()) will elide the copy" No, it won't. It is no more "elision" than using a const&. Nothing is elided, because the language would never have allowed a copy in the first place. Elision, in this context, means to remove something that would have taken place. No copying would happen with a && parameter, and therefore no copying can be elided.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.