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

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

本文关键字:指针 nullptr 设置 析构函数 unique ptr 是否 std      更新时间:2023-10-16

在实现我自己的unique_ptr时(只是为了好玩),我发现它无法通过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 愉快地通过了这个测试文件(当然,这个文件来自 libstdcxx),而 clang 在VERIFY部分失败了。

问题:

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

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

[unique.ptr.single.dtor]

~unique_ptr();
  1. 要求:表达式get_­deleter()(get())应格式正确,应具有明确定义的行为,并且不得引发异常。 [注意:使用default_­delete要求T是完整的类型。 —尾注]

  2. 效果:如果get() == nullptr则没有效果。 否则get_­deleter()(get()).

所以析构函数应该等价于get_deleter()(get()),这意味着b->a不能在A的析构函数中nullptr(delete指令称为内部get_deleter())。


附带说明一下,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首先删除然后分配给nullptr(pointer()),而clang首先分配给nullptr(pointer()),然后删除1


1pointer是对应于Deleter::pointer的别名,如果存在,或者只是T*

libstdc++ 和 libc++ 都是一致的,因为这是定义良好的程序无法观察到的。在析构函数的执行过程中,[res.on.objects]/2 禁止任何尝试观察(或修改,就此而言)unique_ptr对未定义行为的痛苦的状态:

如果访问了标准库类型的对象,并且对象生存期的开始未在访问之前发生,或者访问未在对象的生存期结束之前发生,则除非另有指定,否则未定义行为。

事实上,unique_ptr的析构函数最初(由 LWG2224)添加这一段的原因。

此外,销毁完成后,它所占用的存储内容在 [basic.life]/4 中不确定:

本文档中归属于对象和引用的属性仅适用于给定对象或引用在其生存期内。

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