Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why copy elision not working with std::move?

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?

like image 973
amazingjxq Avatar asked Aug 14 '12 06:08

amazingjxq


2 Answers

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.

like image 127
Mankarse Avatar answered Sep 20 '22 04:09

Mankarse


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.

like image 39
Nawaz Avatar answered Sep 20 '22 04:09

Nawaz