unique_ptr<T>
does not allow copy construction, instead it supports move semantics. Yet, I can return a unique_ptr<T>
from a function and assign the returned value to a variable.
#include <iostream>
#include <memory>
using namespace std;
unique_ptr<int> foo()
{
unique_ptr<int> p( new int(10) );
return p; // 1
//return move( p ); // 2
}
int main()
{
unique_ptr<int> p = foo();
cout << *p << endl;
return 0;
}
The code above compiles and works as intended. So how is it that line 1
doesn't invoke the copy constructor and result in compiler errors? If I had to use line 2
instead it'd make sense (using line 2
works as well, but we're not required to do so).
I know C++0x allows this exception to unique_ptr
since the return value is a temporary object that will be destroyed as soon as the function exits, thus guaranteeing the uniqueness of the returned pointer. I'm curious about how this is implemented, is it special cased in the compiler or is there some other clause in the language specification that this exploits?
Yes, see 12.8 §34 and §35:
Just wanted to add one more point that returning by value should be the default choice here because a named value in the return statement in the worst case, i.e. without elisions in C++11, C++14 and C++17 is treated as an rvalue. So for example the following function compiles with the
-fno-elide-constructors
flagWith the flag set on compilation there are two moves (1 and 2) happening in this function and then one move later on (3).
I think it's perfectly explained in item 25 of Scott Meyers' Effective Modern C++. Here's an excerpt:
Here, RVO refers to return value optimization, and if the conditions for the RVO are met means returning the local object declared inside the function that you would expect to do the RVO, which is also nicely explained in item 25 of his book by referring to the standard (here the local object includes the temporary objects created by the return statement). The biggest take away from the excerpt is either copy elision takes place or
std::move
is implicitly applied to local objects being returned. Scott mentions in item 25 thatstd::move
is implicitly applied when the compiler choose not to elide the copy and the programmer should not explicitly do so.In your case, the code is clearly a candidate for RVO as it returns the local object
p
and the type ofp
is the same as the return type, which results in copy elision. And if the compiler chooses not to elide the copy, for whatever reason,std::move
would've kicked in to line1
.unique_ptr doesn't have the traditional copy constructor. Instead it has a "move constructor" that uses rvalue references:
An rvalue reference (the double ampersand) will only bind to an rvalue. That's why you get an error when you try to pass an lvalue unique_ptr to a function. On the other hand, a value that is returned from a function is treated as an rvalue, so the move constructor is called automatically.
By the way, this will work correctly:
The temporary unique_ptr here is an rvalue.
One thing that i didn't see in other answers isTo clarify another answers that there is a difference between returning std::unique_ptr that has been created within a function, and one that has been given to that function.The example could be like this:
This is in no way specific to
std::unique_ptr
, but applies to any class that is movable. It's guaranteed by the language rules since you are returning by value. The compiler tries to elide copies, invokes a move constructor if it can't remove copies, calls a copy constructor if it can't move, and fails to compile if it can't copy.If you had a function that accepts
std::unique_ptr
as an argument you wouldn't be able to pass p to it. You would have to explicitly invoke move constructor, but in this case you shouldn't use variable p after the call tobar()
.