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?

BenMorel
  • 34,448
  • 50
  • 182
  • 322
amazingjxq
  • 4,487
  • 7
  • 33
  • 35
  • 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. – CB Bailey Aug 14 '12 at 06:07
  • 1
    How many destructor calls did you expect? ;) – curiousguy Aug 14 '12 at 06:29
  • You may want to [read up](http://stackoverflow.com/a/11540204/252000) on what `std::move` actually does. – fredoverflow Aug 14 '12 at 11:25

2 Answers2

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.

Mankarse
  • 39,818
  • 11
  • 97
  • 141
  • +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. – KillianDS Aug 14 '12 at 07:45
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.

Al.G.
  • 4,327
  • 6
  • 31
  • 56
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 6
    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. –  Aug 14 '12 at 06:05
  • @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"*? – Nawaz Aug 14 '12 at 06:08
  • 2
    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. – Björn Pollex Aug 14 '12 at 06:12
  • 2
    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. –  Aug 14 '12 at 06:48
  • @hvd: Your (earlier) comment could be interpreted either way; because you didn't point out which is what, and that is what confused me. But now we all are clear. – Nawaz Aug 14 '12 at 06:50
  • 2
    "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. – Nicol Bolas Aug 14 '12 at 07:02
  • @NicolBolas: I think you're right with the correct wordings ( I meant the same in fact, just wordings aren't that perfect. Let me edit it) – Nawaz Aug 14 '12 at 07:04