c++memory-managementmemory-leaksunique-ptrobject-lifetime

Who and when deletes object when unique_ptr was set to nullptr


I worked with a class with unique_ptr pointing at object and method set it in runetime asynchroniously to nullptr and then another method might call make_unique and set mentioned pointer to this new object.

void Class0::f0()
{
    mPtr = std::make_unique<Class1>();
}

void Class0::f1(SomeType param)
{
    mPtr->doWork(param);
    mPtr      = nullptr; 
}

//f1 called after f0 asynchroniously, any number of times

Who and when deletes this previous that was not explicitly deleted? The unique_ptr is still alive since it's a class field so it's destructor is never called, but unique_ptr can be set to nullptr and make_unique can be called many times. I was almost sure it will cause a memory leak, and mPtr.reset() must be explicitly called first. But I've made small test in visual studio c++, that causes no leak.

void f()
{
    std::unique_ptr<std::vector<std::vector<int>>> ptr = std::make_unique<std::vector<std::vector<int>>>(100000);
    ptr = nullptr;
    f();
}

recursion only to check memory usage with and without ptr = nullptr; I've tried WSL g++ with -O0 with the very same result. Can someone explain it please?


Solution

  • Who and when deletes this previous that was not explicitly deleted?

    Not explicitly deleting the object managed by the smart pointer is the reason to use a smart pointer in the first place. It takes more than deleting the object in the unique_ptrs destructor to properly manage the lifetime of the object (see What is The Rule of Three?). It is rather safe to assume that the smart pointer manages the lifetime correctly unless you do something weird.

    Here ptr = nullptr; is overload (3) from cppreference/unique_ptr/operator=:

    unique_ptr& operator=( std::nullptr_t ) noexcept;     (3)     
    

    Effectively the same as calling reset().

    And reset():

    void reset( pointer ptr = pointer() ) noexcept;
    

    Replaces the managed object.

    Given current_ptr, the pointer that was managed by *this, performs the following actions, in this order:

    • Saves a copy of the current pointer old_ptr = current_ptr
    • Overwrites the current pointer with the argument current_ptr = ptr
    • If the old pointer was non-empty, deletes the previously managed object
      if(old_ptr) get_deleter()(old_ptr).