Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does std::unique_ptr set its underlying pointer to nullptr inside its destructor?

When implementing my own unique_ptr( just for fun), I found it cannot pass this test file from libstdcxx:

struct A;

struct B
{
  std::unique_ptr<A> a;
};

struct A
{
  B* b;
  ~A() { VERIFY(b->a != nullptr); }
};

void test01()
{
  B b;
  b.a.reset(new A);
  b.a->b = &b;
}

gcc passes this test file happily (of course, this file is from libstdcxx), while clang fails for the VERIFY part.

Question:

  1. Is it implementation dependent or undefined behavior?
  2. I guess this postcondition (b->a != nullptr) is important for gcc, otherwise it'll not have a test file for it, but I don't know what's behind it. Is it related to optimization? I know many UB are for better optimizations.
like image 846
Chen Li Avatar asked Jan 17 '19 13:01

Chen Li


People also ask

Can a unique_ptr be Nullptr?

Nullability - a scoped_ptr or unique_ptr can be null, a value object can never be. Polymorphism - a value object is always exactly its static type, but you can substitute in different derived types for a unique_ptr. The previously-held object is automatically destroyed when you do this.

Does unique_ptr call destructor?

Yes. Well the unique ptr has a function object that by default invokes delete on the pointed to object, which calls the destructor. You can change the type of that default deleter to do almost anything.

How is unique_ptr implemented in C++?

unique_ptr allows only one owner of the underlying pointer while shared_ptr is a reference-counted smart pointer. In this implementation, the developer doesn't need to explicitly delete the allocated memory towards the end of the function.

What does unique_ptr get do?

std::unique_ptr::getReturns the stored pointer. The stored pointer points to the object managed by the unique_ptr, if any, or to nullptr if the unique_ptr is empty.


1 Answers

clang (libc++) seems to be non-compliant on this point because the standard says:

[unique.ptr.single.dtor]

~unique_ptr();
  1. Requires: The expression get_­deleter()(get()) shall be well-formed, shall have well-defined behavior, and shall not throw exceptions. [ Note: The use of default_­delete requires T to be a complete type. — end note  ]

  2. Effects: If get() == nullptr there are no effects. Otherwise get_­deleter()(get()).

So the destructor should be equivalent to get_deleter()(get()), which would imply that b->a cannot be nullptr within the destructor of A (which is called inside get_deleter() by the delete instruction).


On a side note, both clang (libc++) and gcc (libstdc++) sets the pointer to nullptr when destroying a std::unique_ptr, but here is gcc destructor:

auto& __ptr = _M_t._M_ptr();
if (__ptr != nullptr)
    get_deleter()(__ptr);
__ptr = pointer();

...and here is clang (call to reset()):

pointer __tmp = __ptr_.first();
__ptr_.first() = pointer();
if (__tmp)
   __ptr_.second()(__tmp);

As you can see, gcc first deletes then assigns to nullptr (pointer()) while clang first assigns to nullptr (pointer()) then delete1.


1pointer is an alias corresponding to Deleter::pointer, if it exists, or simply T*.

like image 62
Holt Avatar answered Oct 16 '22 16:10

Holt