14

对于std::unique_ptrsp1和,和p2有什么区别?std::move()std::unique_ptr::reset()

p1 = std::move(p2);

p1.reset(p2.release());
4

3 回答 3

19

从 [unique.ptr.single.assign]/2 中的移动分配标准规范中,答案应该是显而易见的:

效果:将所有权从 转移u*this,就好像通过调用reset(u.release())后跟从 的分配一样std::forward<D>(u.get_deleter())

显然 move assignment 不一样,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!
}
于 2012-12-13T13:25:33.060 回答
5

例如,如果存在析构函数不匹配,第一个能够警告您。此外,release()是一个非常危险的功能,您的简单示例是正确的,但许多其他用途却不是。最好永远不要使用此功能。

于 2012-12-13T13:04:06.580 回答
-2

我认为第二个版本可能不是异常安全的。它相当于:

auto __tmp = p2.release();
p1.reset(__tmp);

因此,如果调用std::unique_ptr::resetthrows(如果托管对象的删除可能会发生这种情况),那么您将拥有一个永远不会被销毁的未引用对象。在移动分配的情况下,std::unique_ptr可以(并且应该)等待实际移动,直到p1' 的原始对象被正确销毁。

但请注意,这只是一个问题,如果托管对象的析构函数可以抛出,这在几乎所有情况下本身都是错误的,或者如果您使用可能抛出的自定义删除器。所以在实践中,两个代码片段之间通常没有任何行为差异。


编辑:最后乔纳森在他的评论中指出,标准要求自定义删除器不要抛出,这确实使得抛出std::unique_ptr::reset非常不可能/不合格。但他也指出,还有另一个区别,只有一个移动赋值也会移动任何自定义删除器,他也为此写了一个答案。


但不考虑实际产生的行为,两者之间存在巨大的概念差异。如果移动赋值是合适的,那么做一个移动赋值并尽量不要用其他代码来模拟它。事实上,我无法想象任何理由将第一个代码片段一对一地替换为第二个。DeadMG是正确的,std::unique_ptr::release只有在您真正知道自己在做什么以及在哪个上下文中处理非托管动态对象时才应该使用它。

于 2012-12-13T13:15:03.827 回答