对于std::unique_ptr
s p1
和p2
,std::move()
和std::unique_ptr::reset()
之间有什么区别?
p1 = std::move(p2);
p1.reset(p2.release());
答案 0 :(得分:16)
答案应该从标准的[unique.ptr.single.assign] / 2中的移动分配规范中明显看出:
效果:将所有权从
u
转移到*this
,就像通过调用reset(u.release())
,然后从std::forward<D>(u.get_deleter())
分配一样。
显然,移动分配与reset(u.release())
不同,因为它会执行额外的操作。
附加效果非常重要,如果没有它,您可以使用自定义删除器获得未定义的行为:
#include <cstdlib>
#include <memory>
struct deleter
{
bool use_free;
template<typename T>
void operator()(T* p) const
{
if (use_free)
{
p->~T();
std::free(p);
}
else
delete p;
}
};
int main()
{
std::unique_ptr<int, deleter> p1((int*)std::malloc(sizeof(int)), deleter{true});
std::unique_ptr<int, deleter> p2;
std::unique_ptr<int, deleter> p3;
p2 = std::move(p1); // OK
p3.reset(p2.release()); // UNDEFINED BEHAVIOUR!
}
答案 1 :(得分:5)
例如,第一个能够警告您是否存在析构函数不匹配。另外,release()
是一个非常危险的函数,你的小例子是正确的,但许多其他用途都没有。最好永远不要使用这个功能。
答案 2 :(得分:-2)
我认为第二个版本可能不是例外安全。它相当于:
auto __tmp = p2.release();
p1.reset(__tmp);
因此,如果对std::unique_ptr::reset
的调用抛出(如果删除托管对象,则可能就是这种情况),那么您将拥有一个不会被破坏的未推荐对象。在移动分配的情况下,std::unique_ptr
可以(并且应该)等待实际移动,直到p1
的原始对象被正确销毁。
但请注意,如果托管对象的析构函数可以抛出,这几乎是一个问题,几乎在所有情况下本身都是错误的,或者如果您使用可能抛出的自定义删除器。因此在实践中,两个代码片段之间通常没有任何行为差异。
编辑:最后 Jonathan 在评论中指出,标准要求自定义删除器不要抛出,这确实会导致{{{ 1}}非常不可能/不符合。但他也指出还有另一个区别,因为只有一个移动任务也可以移动任何自定义删除者,他也写了答案。
但是无视实际产生的行为,两者之间存在巨大的概念差异。如果适当的移动分配,然后执行移动分配,并尝试不通过其他代码模拟它。实际上,我无法想象任何理由在第二个代码片段中一对一地替换第一个代码片段。 DeadMG 是正确的,std::unique_ptr::reset
只有在你真正知道自己在做什么以及在哪种环境中搞乱非托管动态对象时才应该使用。