在实现自己的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会很高兴地看到这个测试文件(当然,这个文件来自libstdcxx),而VERIFY
的部分是clang fails。
问题:
b->a != nullptr
)对于gcc很重要,否则它将没有测试文件,但我不知道它的背后是什么。与优化有关吗?我知道很多UB都是为了更好的优化。答案 0 :(得分:8)
clang
(libc ++)在这一点上似乎不合规,因为该标准指出:
~unique_ptr();
要求:表达式
get_deleter()(get())
格式正确,行为明确,并且不会引发异常。 [注意:使用default_delete
要求T
是完整类型。 — 尾注 ]效果:如果
get() == nullptr
没有效果。 否则为get_deleter()(get())
。
因此,析构函数应等效于get_deleter()(get())
,这意味着b->a
不能在nullptr
的析构函数中被A
(在{{1}内部调用) },方法是get_deleter()
。
另一方面,delete
(libc ++)和clang
(libstdc ++)在销毁gcc
时都将指针设置为nullptr
,但这是{{1 }}析构函数:
std::unique_ptr
...这是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
),然后删除 1 。
1 nullptr
是与pointer()
对应的别名(如果存在),或者简称为pointer
。
答案 1 :(得分:3)
libstdc++ 和 libc++ 都是一致的,因为这是一个定义良好的程序无法观察到的。在析构函数的执行过程中,[res.on.objects]/2 禁止在未定义行为的痛苦中观察(或修改,就此而言)unique_ptr
的状态:
如果访问标准库类型的对象,并且对象生命周期的开始没有在访问之前发生,或者访问没有在对象生命周期结束之前发生,除非另有说明,否则行为未定义。
事实上,unique_ptr
的析构函数是为什么首先添加了这个段落(由 LWG2224)。
另外,销毁完成后,它占用的存储内容由[basic.life]/4不确定:
<块引用>本文档中赋予对象和引用的属性仅适用于给定对象或引用在其生命周期内。
答案 2 :(得分:0)
销毁后std::unique_ptr<>
占用的内存的最终状态没有要求。将其设置为null毫无意义,因为内存将返回到分配它的位置。 GCC可能会检查它是否为非null,以确保没有人添加不必要的代码来清除它。在适当的情况下,在不需要时强制清除该值可能会导致性能下降。