0

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

ChrisB
  • 1,540
  • 6
  • 20
Darshan Bhat
  • 245
  • 1
  • 11
  • you seem to confuse destructor of `ptr2` with destructor of the contained object. `ptr2` is only destroyed when `main` returns not before – 463035818_is_not_an_ai Apr 25 '23 at 07:32
  • "// I know this is violationg rule of 5" no you arent. Its a perfectly fine rule of 0 example with some custom implementation of a destructor that does nothing harmful – 463035818_is_not_an_ai Apr 25 '23 at 07:34
  • Take a look at [cppreference's documentation](https://en.cppreference.com/w/cpp/memory/unique_ptr/operator%3D) for `std::unique_ptr::operator=`. The call is similar to `ptr2.reset(ptr1.release())`. This does not entail the creation of a new `ElfData` object. – Nathan Pierson Apr 25 '23 at 07:34
  • Yes if there is previously owned object of ptr2. No, you will have 2 ElfData("Ptr1") objects after replacement. – Öö Tiib Apr 25 '23 at 07:35
  • @463035818_is_not_a_number I am not confused with the destruction of ptr2. I clearly mentioned will the assignment call the destructor of previously owning object of ptr2 – Darshan Bhat Apr 25 '23 at 07:35
  • 2
    you write "// Will this always call the destructor of ptr2 ?" but `ptr2` is certainly not destroyed before `main` returns. In text you ask for destruction of the contained object, in title you ask for destructor of the unique ptr. Destruction of the unique ptr and destruction of the contained object is related but not the same – 463035818_is_not_an_ai Apr 25 '23 at 07:37
  • 2
    I know this is sort of nitpicky, but the title question can always be answered with *no*, since std::move only reinterprets lvalue reference as rvalue references. The actual move happens in the move constructor / move assignment operator. – ChrisB Apr 25 '23 at 07:37
  • I see that "I consulted reference materials about C++" is nowhere on your list. – Nathan Pierson Apr 25 '23 at 07:55

1 Answers1

4

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.

ChrisB
  • 1,540
  • 6
  • 20