Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 852
Dundo Avatar asked Aug 13 '21 09:08

Dundo


People also ask

Why is std :: move necessary?

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.

What happens when you move a unique_ptr?

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.

Why do we need unique_ptr?

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.

Is unique_ptr null after move?

Yes, you can compare it to nullptr after the move and it is guaranteed to compare equal.


1 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.

like image 165
Daniel Langr Avatar answered Oct 22 '22 12:10

Daniel Langr