Search code examples
c++unique-ptrnullptr

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.

Solution

  • 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.


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