Kornel Szymkiewicz
Kornel Szymkiewicz

Reputation: 682

Validness of unique_ptr passed as a rvalue reference to a function (inheritance)

I have the following code and I'm trying to understand why the unique_ptr base variable remains valid after calling foo(). However, I haven't found an answer so far.

#include <memory>
#include <iostream>

struct Base
{
};

struct Derived : public Base
{
};

void foo(std::unique_ptr<Base>&& b)
{
}

int main()
{
    std::unique_ptr<Derived> derived = std::make_unique<Derived>();
    foo(std::move(derived));
    std::cout << "derived after foo(): " << (derived? "valid\n" : "invalid\n");

    std::unique_ptr<Base> base = std::make_unique<Derived>();
    foo(std::move(base));
    std::cout << "base after foo(): " << (base? "valid\n" : "invalid\n");


    return 0;
}

which gives the output:

derived after foo(): invalid
base after foo(): valid

Could someone please explain why base is still valid after calling foo() while derived becomes invalid?

Upvotes: 8

Views: 221

Answers (2)

NathanOliver
NathanOliver

Reputation: 180385

First, std::move() doesn't move anything, it just casts the object into an rvalue reference so it can be passed to a function that take an rvalue reference.

Second, this code also doesn't move anything:

void foo(std::unique_ptr<Base>&& b)
{
}

b is an rvalue reference, and you never move it into anything, so after calling foo the passed in object is not changed.

Now, why does foo(std::move(derived)); actually move derived? That happens because std::unique_ptr<Base> and std::unique_ptr<Derived> are distinct types. This means when you pass std::move(derived) then that is used to initialize a temporary std::unique_ptr<Base>, and it is that temporary that gets passed to foo(). A consequence of that is derived gets moved into that temporary object.

That is why base and derived behave differently.

Upvotes: 17

sehe
sehe

Reputation: 392763

If you intend to pass ownership, the best practice is usually to pass by value:

void foo(std::unique_ptr<Base>) {}

Otherwise you end up just passing by reference if the type matched 1:1, and moving if you happened to cause an implicit conversion. Keep in mind rvalue-references are still just references.

Live On Coliru

#include <iostream>
#include <memory>

struct Base {};

struct Derived : public Base {};

void foo(std::unique_ptr<Base>) {}

int main() {
    std::unique_ptr<Derived> derived = std::make_unique<Derived>();
    foo(std::move(derived));
    std::cout << "derived after foo(): " << (derived ? "valid\n" : "invalid\n");

    std::unique_ptr<Base> base = std::make_unique<Derived>();
    foo(std::move(base));
    std::cout << "base after foo(): " << (base ? "valid\n" : "invalid\n");
}

Printing

derived after foo(): invalid
base after foo(): invalid

Upvotes: 7

Related Questions