Search code examples
c++smart-pointersunique-ptrstdmove

Will std::move() call the destructor of the owned object of a unique_ptr?


Consider the following code:

#include <memory>
#include <iostream>
#include <string>

struct Data {
    std::string name;
    Data(std::string aName): name(aName) {
        std::cout << name << " Constructor is called\n";
    }
   ~Data() {
        std::cout << name << " Destructor is called \n";
    }
};

int main() {

    std::unique_ptr<Data> ptr1(new Data("Data1"));
    std::unique_ptr<Data> ptr2(new Data("Data2"));

    // Will this always call the destructor of the object owned by ptr2 ?
    ptr2 = std::move(ptr1);
    return 0;
}

Wil the line ptr2 = std::move(ptr1) always call the destructor of the previously owned object of ptr2? If this is true, can we replace the code below:

std::unique_ptr<Data> ptr1(new Data("Data1"));
ptr2 = std::move(ptr1);

with

ptr2.reset(new Data("Data1"));

?

I tried searching in cpp reference doc and the similar questions in other forums, but I could not get a clarity on this.

Link to godbolt : https://godbolt.org/z/cYhKMWdsT


Solution

  • For demonstration, I extended your example a bit and added a simple wrapper around unique_ptr:

    #include <memory>
    #include <iostream>
    #include <string>
    
    struct Data {
        std::string name;
        Data(std::string aName) : name(aName) {
            std::cout<< name << " Constructor is called\n";
        }
       ~Data() {
            std::cout<< name << " Destructor is called\n";
       }
    };
    
    struct DataPtr{
        std::unique_ptr<Data> ptr;
        DataPtr(Data* data): ptr(data){}
        void operator=(DataPtr&& v){
            std::cout << "unique_ptr to " << ptr->name << " reassigned to " << v.ptr->name << "\n";
            ptr = std::move(v.ptr);
        }
    };
    
    int main() {
        DataPtr ptr1(new Data("Data1"));
        DataPtr ptr2(new Data("Data2"));
        ptr2 = std::move(ptr1);
        std::cout<< "End of Scope\n";
        return 0;
    }
    

    This produces the following output:

    Data1 Constructor is called
    Data2 Constructor is called
    unique_ptr to Data2 reassigned to Data1
    Data2 Destructor is called
    End of Scope
    Data1 Destructor is called
    

    (https://godbolt.org/z/nnK11nfq3)

    As you can see, the moment that ptr2 is reassigned, Data2 is destructed, because the unique pointer that contained it is reassigned. (It's move assignment operator is called, which calls delete on the previously contained object).

    The final result will be equivalent to the reset alternative you propose.

    It is worth noting that this is in fact a consequence of the move assignment operator being called, not std::move, which simply reinterprets the type of the reference as an rvalue reference, but otherwise has no side effects.