对于std::unique_ptr
s p1
和p2
,std::move()
和std::unique_ptr::reset()
之间有什么区别?std :: move和unique_ptr :: reset有什么区别?
p1 = std::move(p2);
p1.reset(p2.release());
对于std::unique_ptr
s p1
和p2
,std::move()
和std::unique_ptr::reset()
之间有什么区别?std :: move和unique_ptr :: reset有什么区别?
p1 = std::move(p2);
p1.reset(p2.release());
答案应该是从[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!
}
例如,第一个能够警告您是否存在析构函数不匹配。此外,release()
是一个非常危险的功能,你的微不足道的例子是正确的,但许多其他用途不是。最好永远不要使用这个功能。
我想,第二个版本可能不是异常安全的。它等效于:
auto __tmp = p2.release();
p1.reset(__tmp);
因此,如果调用std::unique_ptr::reset
抛出(这可能是这种情况,如果管理对象的缺失抛出),那么你有这将不会永远摧毁一个unreferred对象。在移动分配的情况下,std::unique_ptr
可以(也应该)等待实际移动,直到p1
的原始对象被正确销毁。
但是请注意,如果托管对象的析构函数可能抛出(几乎在所有情况下都是错误的),或者如果使用可能抛出的自定义删除器,这只是一个问题。所以在实践中,两个代码片段之间通常没有任何行为差异。
编辑:最终乔纳森在他的评论中指出,该定制删除是由标准要求不扔,这的确使得std::unique_ptr::reset
件不可思议的事/不符合的投掷。但他也指出,还有一个区别,即只有一个移动任务也会移动任何定制的删除者,他也为此写了一个答案。
但是无视实际产生的行为,两者之间存在巨大的概念差异。如果移动分配合适,则然后执行移动分配,并尽量不要通过其他代码模拟它。实际上,我无法想象任何理由都会在第二个代码片段中一对一替换第一个代码片段。 DeadMG是正确的,std::unique_ptr::release
只应用于如果你真的知道你在做什么以及在哪个上下文中搞乱了非托管动态对象。
_“所以在实践中,两个代码片段之间通常没有任何行为差异。”这不是真的,一个移动删除器,一个不移动。如果删除者是有状态的,这非常重要。 –
你知道,'std :: unique_ptr'的移动赋值运算符被指定为“通过调用'reset(u.release())'''将所有权从'u'转移到'* this', ”。该标准严重依赖于不会抛出的析构函数。 – Xeo
@JonathanWakely哈,对!没有太多关于定制删除器的想法。 –
TL; DR:第2形态被打破。永远不要使用它。 –