【发布时间】:2012-12-01 08:45:25
【问题描述】:
对于std::unique_ptrs p1和p2,std::move()和std::unique_ptr::reset()有什么区别?
p1 = std::move(p2);
p1.reset(p2.release());
【问题讨论】:
-
TL;DR: 第二种形式坏了。永远不要使用它。
标签: c++ c++11 unique-ptr
对于std::unique_ptrs p1和p2,std::move()和std::unique_ptr::reset()有什么区别?
p1 = std::move(p2);
p1.reset(p2.release());
【问题讨论】:
标签: c++ c++11 unique-ptr
从 [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!
}
【讨论】:
p3的deleter,use_free是未初始化的?
delete p,这对于从malloc获取的指针是错误的。
我认为第二个版本可能不是异常安全的。相当于:
auto __tmp = p2.release();
p1.reset(__tmp);
因此,如果对std::unique_ptr::reset 的调用抛出(如果托管对象的删除抛出抛出),那么您将拥有一个永远不会被销毁的未引用对象。在移动分配的情况下,std::unique_ptr 可以(并且应该)等待实际移动,直到 p1 的原始对象被正确销毁。
但是请注意,这只是一个问题,如果托管对象的析构函数可以抛出,这在几乎所有情况下本身都是错误的,或者如果您使用可能会抛出的自定义删除器。所以在实践中,两个代码 sn-ps 之间通常没有任何行为差异。
编辑:最后 Jonathan 在他的评论中指出,标准要求自定义删除器不要抛出,这确实导致了 @987654325 的抛出@ 不太可能/不合格。但他也指出还有另一个区别,只有一个移动赋值也会移动任何自定义删除器,他也为此写了一个答案。
但不考虑实际产生的行为,两者之间存在巨大的概念差异。如果移动分配合适,然后进行移动分配,并尽量不要用其他代码来模拟它。事实上,我无法想象任何理由将第一个代码 sn-p 一对一地替换为第二个。 DeadMG 是正确的,std::unique_ptr::release 只有在您真正知道自己在做什么以及在哪个上下文中处理非托管动态对象时才应该使用。
【讨论】:
std::unique_ptr 的移动赋值运算符被指定为“将所有权从 u 转移到 *this,就像通过调用 reset(u.release()) [...]”。该标准严重依赖不抛出的析构函数。
例如,如果存在析构函数不匹配,第一个能够警告您。此外,release() 是一个非常危险的函数,您的简单示例是正确的,但许多其他用途却不是。最好永远不要使用此功能。
【讨论】:
reset 没有任何先决条件,因此您始终可以调用它,因为任何移动都指定将移动的对象留在未定义但有效状态,这使得任何没有先决条件的函数的使用始终有效。只是在依赖某些先决条件时(例如指针为nullptr,但标准可能保证std::unique_ptr),这需要您首先检查相应的条件。移动不会以任何方式使对象失效,这是一种常见的误解。
unique_ptr<T, Del>。几乎任何其他事情都会导致诸如删除器不匹配、内存泄漏或双重删除之类的事情。