std::unique_ptr 是否在其析构函数中将其底层指针设置为 nullptr?

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

在实现我自己的 unique_ptr(只是为了好玩)时,我发现它无法从 libstdcxx:

传递这个 test file
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 对于 VERIFY 部分。

问题:

  1. 它是依赖于实现还是未定义的行为?
  2. 我想这个后置条件(b->a != nullptr)对gcc很重要,否则它不会有测试文件,但我不知道它背后的内容。跟优化有关系吗?我知道很多UB都是为了更好的优化。

clang (libc++) 在这一点上似乎不符合标准,因为标准说:

[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()).

所以析构函数应该等同于 get_deleter()(get()),这意味着 b->a 不能是 A 的析构函数中的 nullptr(在 get_deleter() 通过 delete 指令)。


附带说明一下,clang (libc++) 和 gcc (libstdc++) 在销毁 std::unique_ptr 时都将指针设置为 nullptr,但这里是 gcc 析构函数:

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

...这里是 clang(调用 reset()):

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

可以看到,gcc先删除再赋值给nullptrpointer()),而clang先赋值给nullptrpointer()) 然后删除1.


1 pointer是对应Deleter::pointer的别名,如果存在的话,或者干脆就是T*.

std::unique_ptr<>销毁后占用内存的最终状态没有要求。将它设置为 null 是没有意义的,因为内存正在返回到它分配的位置。 GCC 可能会检查它是否不为空,以确保没有人添加不必要的代码来清除它。在适当的情况下,在不需要时强制清除该值可能会导致性能下降。

libstdc++ 和 libc++ 都符合标准,因为这是定义明确的程序无法观察到的。在析构函数的执行期间,[res.on.objects]/2 禁止任何试图观察(或修改,就此而言)unique_ptr 的状态,以免出现未定义的行为:

If an object of a standard library type is accessed, and the beginning of the object's lifetime does not happen before the access, or the access does not happen before the end of the object's lifetime, the behavior is undefined unless otherwise specified.

事实上,unique_ptr 的析构函数 为什么首先添加此段的原因(LWG2224)。

另外,销毁完成后,其占用的存储内容由[basic.life]/4不确定:

The properties ascribed to objects and references throughout this document apply for a given object or reference only during its lifetime.