Why do I need to move `std::unique_ptr`

Viewed 1528

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.

2 Answers

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.

Because there should only be one valid unique_ptr at any one time. That is why it is called unique_ptr.

unique_ptr is un-copyable, you must move it.
Otherwise you would end up with a copy of the pointer which would defeat the point of it being unique!
See: Rules for Smart Pointers

Related