1

As the following code presents, I tried to delete the object by the raw pointer get from a unique_ptr. But, as the output shows, the complier reported errors. However, for raw pointers, we can do this int *p1 = new int{100}; int* p2 = p1; delete p2;.

Besides, I thought that unique_ptr maintain its ownership by move semantics. As the get() function of unique_ptr returns the raw pointer, how does the unique_ptr can still have the ownership of the object. Meanwhile, why the raw pointer doesn't get the ownership. At the same time, I am confusing how does this implemented. Thanks.

#include <iostream>
#include <memory>

int main(int argc, char const *argv[])
{
    std::unique_ptr<int> newPtr = std::make_unique<int>(1234);
    std::cout << *newPtr << std::endl;
    int* rawInt = newPtr.get();
    *rawInt = 200;
    delete rawInt;
    rawInt = nullptr;
    std::cout << *newPtr << std::endl; 
    return 0;
}

The code was performed on MacOs and the output is:

the output

amont
  • 81
  • 8
  • The `get` function returns the raw pointer without relinquishing ownership. It's there so that other stuff can get a look at the pointed-to object without worrying about deleting it. If you want to get the pointer and tell the `unique_ptr` "you're not managing this anymore", you want `release`. – Nathan Pierson May 05 '22 at 05:04
  • @NathanPierson I known `release`, but I confused is that since I can get the raw pointer, why can't get the ownership. How does the unique_ptr maintain ownership actually. – amont May 05 '22 at 05:07
  • It maintains the ownership unless you tell it to do something that will cause it to relinquish or transfer the ownership. `release` tells it to relinquish the ownership. Move-constructing a new `unique_ptr` will transfer the ownership from the old one to the newly-constructed one. Move-assigning to another `unique_ptr` will transfer ownership. But most emphatically, just because you have the raw pointer does not mean you have ownership of the pointed-to object. (If you did, you would just be using raw pointers for ownership in the first place.) – Nathan Pierson May 05 '22 at 05:09
  • @NathanPierson Why delete the raw pointer came errors, while plain pointers don't. I still confused how does that implemented. – amont May 05 '22 at 05:12
  • 1
    Because when you do `int* rawInt = newPtr.get(); delete rawInt;`, you never told `newPtr` to stop having ownership of `*rawInt`. So when `newPtr` is destroyed, it attempts to `delete` the same pointer that you already deleted. That's double deletion, and it's undefined behavior, which in your case showed up as an error message and an aborted process. – Nathan Pierson May 05 '22 at 05:14
  • @NathanPierson I got it. Thanks. What I understood was `delete rawInt;` made the error. – amont May 05 '22 at 05:16
  • @NathanPierson what confused me personally is that 200 gets printed after the pointer is deleted. Shouldn't the memory be deallocated by then? How come we can still access it? – greghvk May 05 '22 at 06:17
  • @greghvk Dereferencing a deleted pointer is undefined behavior, anything is allowed to happen up to and including behaving the same as if there wasn't any UB at all. – Kyle May 05 '22 at 06:25
  • @Kyle what does the full name of UB, thanks – amont May 05 '22 at 06:52
  • 1
    It is undefined behavior. It means, if a program does specific things wrong, it is invalid. For most of those cases the compiler can generate a warning, some of those the programmer has to take care of even without warning. Sometimes UB have compatibility reasons (different platforms or historically), sometimes performance reasons, sometimes to keep the language itself or the compilers simple. If the program is invalid, anything can happen, and it can vary from run to run of the program: Including crashes, wrong computation, but also that the program sometimes works. – Sebastian May 05 '22 at 08:27

1 Answers1

5

std::unique_ptr is a really simple class. Conceptually, it's basically just this:

template <typename T>
class unique_ptr
{
private:
    T* ptr;

public:
    unique_ptr(T* p) ptr{p} {}
    ~unique_ptr() { delete ptr; }

    T* get() { return ptr; }
    T* release() {
        T* p = ptr;
        ptr = nullptr;
        return p;
    }

    // other stuff
};

It just has a pointer member, and deletes the pointed-to object in its destructor. In reality there's a bit more to it, but that's essentially it.

The get member just returns a copy of the unique_ptr's managed pointer. That's it. Since the unique_ptr's pointer member is still pointing to that object, its destructor will still delete the object. If you also delete that object via another pointer to it then it will get deleted twice, which results in undefined behavior.

The release member function, on the other hand, sets the unique_ptr's pointer member to nullptr before returning a copy of its original value. Since its member pointer is null, its destructor won't delete anything.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52