I'm used to not use std::move
when returning a std::unique_ptr
, because doing so prohibits RVO. I have this case where I have a local std::unique_ptr
, but the return type is a std::shared_ptr
.
Here's a sample of the code:
shared_ptr<int> getInt1() {
auto i = make_unique<int>();
*i = 1;
return i;
}
shared_ptr<int> getInt2() {
return make_unique<int>(2);
}
unique_ptr<int> getInt3() {
auto ptr = make_unique<int>(2);
return ptr;
}
int main() {
cout << *getInt1() << endl << *getInt2() << *getInt3() << endl;
return 0;
}
GCC accepts both cases, but Clang refuses the getInt1()
With this error:
main.cpp:10:13: error: no viable conversion from 'std::unique_ptr<int, std::default_delete<int> >' to 'shared_ptr<int>'
return i;
^
Here's both cases on coliru: GCC, Clang
Both compiler accept the third case.
Which one is wrong? Thanks.
If a function returns a std::unique_ptr<> , that means the caller takes ownership of the returned object. class Base { ... }; class Derived : public Base { ... }; // Foo takes ownership of |base|, and the caller takes ownership of the returned // object.
Use unique_ptr when if you want to have single ownership(Exclusive) of resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. Use shared_ptr if you want to share ownership of resource .
So the best way to return a shared_ptr is to simply return by value: shared_ptr<T> Foo() { return shared_ptr<T>(/* acquire something */); }; This is a dead-obvious RVO opportunity for modern C++ compilers. I know for a fact that Visual C++ compilers implement RVO even when all optimizations are turned off.
A unique_ptr does not share its pointer. It cannot be copied to another unique_ptr , passed by value to a function, or used in any C++ Standard Library algorithm that requires copies to be made. A unique_ptr can only be moved.
std::unique_ptr
could be used to construct std::shared_ptr
only when it's a rvalue. See the constructor declaration of std::shared_ptr
:
template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r );
So you need to use std::move
to make the 1st case work, otherwise it should fail.
return std::move(i);
BTW: I compiled the code with gcc 4.9.3 it failed either.
source_file.cpp:14:12: error: cannot bind ‘std::unique_ptr<int, std::default_delete<int> >’
lvalue to ‘std::unique_ptr<int, std::default_delete<int> >&&’
return i;
^
The correct answer depends on which C++ standard you are talking about.
If we are talking about C++11, clang is correct (an explicit move is needed). If we are talking about C++14, gcc is correct (an explicit move is not needed).
C++11 says in N3290/[class.copy]/p32:
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. If overload resolution fails, ...
This demands that you only get the implicit move when the return expression has the same type as the function return type.
But CWG 1579 changed this, and this defect report was accepted after C++11, and in time for C++14. This same paragraph now reads:
When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a
return
statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, ...
This modification basically allows the return expression type to be convertible-to the function return type and still be eligible for implicit move.
Does this mean that the code needs a #if
/#else
based on the value of __cplusplus
?
One could do that, but I wouldn't bother. If I were targeting C++14, I would just:
return i;
If the code is unexpectedly run under a C++11 compiler, you will be notified at compile-time of the error, and it is trivial to fix:
return std::move(i);
If you are just targeting C++11, use the move
.
If you want to target both C++11 and C++14 (and beyond), use the move
. The downside of using move
gratuitously is that you can inhibit RVO (Return Value Optimization). However, in this case, RVO is not even legal (because of the conversion from the return
statement to the return type of the function). And so the gratuitous move
does not hurt anything.
The one time you might lean towards a gratuitous move
even when targeting C++14 is if without it, things still compile in C++11, and invoke an expensive copy conversion, as opposed to a move conversion. In this case, accidentally compiling under C++11 would introduce a silent performance bug. And when compiled under C++14 the gratuitous move
still has no detrimental effects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With