kzaiwo
kzaiwo

Reputation: 1748

Returning unique_ptr variable returns an error

I came across a weird problem. I get an error message in setGetDog() method when written this way:

class Dog
{
    public:
    void bark() { std::cout << "Bark!" << std::endl; }
};

class test
{
    public:
    std::unique_ptr<Dog> setGetDog()
    {
        if (!dog_)
        {
            dog_ = std::make_unique<Dog>();
        }
        return dog_;
    }

    private:
    std::unique_ptr<Dog> dog_;
};

int main()
{
    auto testClass = std::make_unique<test>();
    auto dog = testClass->setGetDog();
}

Error is like this:

error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Dog; _Dp = std::default_delete<Dog>]'

But when I change the implementation to:

    std::unique_ptr<Dog> setGetDog()
    {
        return std::make_unique<Dog>();
    }

It works just fine.

What is the matter with dog_? I do not understand why the error states that it is deleted when in fact test is still alive when the method was called (and so dog_ as well).

I know I can write this some other way but I'm specifically curious on why behavior is like this on this implementation.

Can anyone please enlighten me?

Upvotes: 1

Views: 336

Answers (2)

NathanOliver
NathanOliver

Reputation: 180415

There are a few different types of value categories/situations that objects have that affects how there are returned from a function. Lets look at the rvalue category first. When you do

std::unique_ptr<Dog> setGetDog()
{
    return std::make_unique<Dog>();
}

std::make_unique<Dog>() returns by value making the return object an rvalue. That means that when the compiler returns the object it will implicitly move it. (the compiler can also elide the copy but that doesn't matter for this discussion) Since the object is moved you don't have any issues since std::unique_ptr is movable.

The second case is when you have an lvalue, but it is a function local object. If you had

std::unique_ptr<Dog> setGetDog()
{
    auto ptr = std::make_unique<Dog>();
    return ptr;
}

Here ptr is an lvalue but it is going away so we can call it an xvalue(expiring value). With xvalues, when you return them the compiler tries to move it, since moving it is a safe operation in this case. If the move operation does not exist/is not viable, then the compiler falls back to copying. Since std::unique_ptr is movable, it gets moved without error.

The last case is when you have a plain lvalue like you have with

class test
{
    public:
    std::unique_ptr<Dog> setGetDog()
    {
        if (!dog_)
        {
            dog_ = std::make_unique<Dog>();
        }
        return dog_;
    }

    private:
    std::unique_ptr<Dog> dog_;
};

Here, dog_ is a member of the class, not a object local to the function. This means the only thing the compiler can do is try and make a copy. Since you can't copy a unique_ptr, you get an error. You would need return std::move(dog_); for it to compile, but if you did that dog_ in the class would be empty.

Upvotes: 3

Paul Rooney
Paul Rooney

Reputation: 21609

When you just return the unique_ptr then return value optimization can be applied. This is because the unique_ptr can actually be constructed at the address of the unique_ptr instance in the calling function. The compiler allows this as no copying is occurring.

When you add the if, the function may or may not be trying to return an existing instance. It's a runtime decision. An existing instance wont have the opportunity to be constructed where the calling function wants it and so must be copied.

unique_ptr has a deleted copy constructor to preserve the unique part of its contract, so the code does not compile.

Depending on your needs you may want to use a shared_ptr, use std::move (mistake probably, it empties the unique_ptr inside your class) or use value semantics to copy the actual object and not the pointer to it.

Upvotes: 1

Related Questions