Given the following code:
#include <iostream>
#include <memory>
struct A {};
struct B : public A {};
std::pair<bool, std::unique_ptr<B>> GetBoolAndB() {
return { true, std::make_unique<B>() };
}
std::unique_ptr<A> GetA1() {
auto[a, b] = GetBoolAndB();
return b;
}
std::unique_ptr<A> GetA2() {
auto [a, b] = GetBoolAndB();
return std::move(b);
}
GetA1
does not compile, with this error:
C2440: 'return': cannot convert from 'std::unique_ptr<B,std::default_delete<_Ty>>' to 'std::unique_ptr<A,std::default_delete<_Ty>>'
while GetA2
does compile without errors.
I don't understand why I need to call std::move
to make the function work.
Edit
Just to clarify, as pointed out in comments by DanielLangr, my doubt was about the fact that
std::unique_ptr<A> GetA3() {
std::unique_ptr<B> b2;
return b2;
}
compiles and transfer ownership without the need for std::move
.
Now I understand that in case of GetA1
and GetA2
, with structured bindings it happens that b
is part of some object, and so it must be moved to become an rvalue reference.
std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.
A unique_ptr can only be moved. This means that the ownership of the memory resource is transferred to another unique_ptr and the original unique_ptr no longer owns it.
Use unique_ptr when you want to have single ownership(Exclusive) of the 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. A shared_ptr is a container for raw pointers.
Yes, you can compare it to nullptr after the move and it is guaranteed to compare equal.
I don't understand why I need to call std::move to make the function work.
Because the corresponding constructor of std::unique_ptr
has a parameter of rvalue reference type:
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept;
See documentation for details: https://en.cppreference.com/w/cpp/memory/unique_ptr/unique_ptr
Since rvalue references cannot bind lvalues, consequently, you cannot use b
(which is lvalue) as an argument of this constructor.
If you wonder why b
is treated as lvalue in the return
statement, see, for example: Why Structured Bindings disable both RVO and move on return statement? In short, b
is not a variable with automatic storage duration, but a reference to a pair element instead.
The error message basically just says that the compiler could not find any viable converting constructor, therefore, it "cannot convert...".
By wrapping b
with std::move
call, you are creating an expression that refers to the very same object as b
, but its category is rvalue. Which may be bound with that constructor parameter.
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