Search code examples
c++c++11move-semanticsunique-ptrobject-lifetime

Why is unique_ptr null?


In the code snippet below, the assertion in foo always fires.

Can anyone explain why y is a nullptr? It looks like a lifetime issue, i.e. y is destroyed between the calls to put and get but I don't really understand why.

What am I missing?

TIA

class Y
{
public:
    Y(const std::string& name) : m_name(name)
    {
    }

    const std::string& getName() const
    {
        return m_name;
    }

private:
    Y(const Y&);

    const std::string m_name;
};

void foo(std::unique_ptr<Y> y)
{
    if (y == nullptr)
    {
        // ALWAYS FIRES
        assert(false && "nullptr\n");
    }

    std::string name = y->getName();
    std::cout << "name: " << name << "\n";
}

class X
{
public:
    X() {}

    void put(std::unique_ptr<Y> y)
    {
        m_queue.push([&] { foo(std::move(y)); });
    }

    void get()
    {
        std::function<void()> func = m_queue.front();
        func();
    }

private:
    std::queue<std::function<void()>> m_queue;
};


int main()
{
    std::unique_ptr<Y> y(new Y("fred"));
    X x;
    x.put(std::move(y));
    x.get();
    return 0;
}

Solution

  • void put(std::unique_ptr<Y> y)
    {
        m_queue.push([&] { foo(std::move(y)); });
    }
    

    Here, you push a lambda that contains a reference to the local variable y. The moment you leave put, the local variable is destroyed, and the lambda contains a dangling reference. Any further behavior is undefined.

    You would need to capture the local variable by moving it into the lambda, but that is quite advanced, and also insufficient, because std::function cannot hold move-only function objects. The easiest way to solve this is to switch from unique_ptr to shared_ptr and capturing by value in the lambda.