【问题标题】:what are the differences between std::move and unique_ptr::reset?std::move 和 unique_ptr::reset 有什么区别?
【发布时间】:2012-12-01 08:45:25
【问题描述】:

对于std::unique_ptrs p1p2std::move()std::unique_ptr::reset()有什么区别?

p1 = std::move(p2);

p1.reset(p2.release());

【问题讨论】:

  • TL;DR: 第二种形式坏了。永远不要使用它。

标签: c++ c++11 unique-ptr


【解决方案1】:

从 [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!
}

【讨论】:

  • 你的意思是对于p3deleteruse_free是未初始化的?
  • @chaosink 不,它被初始化为false,这意味着它将执行delete p,这对于从malloc获取的指针是错误的。
【解决方案2】:

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

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 只有在您真正知道自己在做什么以及在哪个上下文中处理非托管动态对象时才应该使用。

【讨论】:

  • “所以在实践中,两个代码 sn-ps 之间通常没有任何行为差异。”这不是真的,一个移动删除器,一个不移动.如果删除器是有状态的,这一点非常重要。
  • 你知道,std::unique_ptr 的移动赋值运算符被指定为“将所有权从 u 转移到 *this,就像通过调用 reset(u.release()) [...]”。该标准严重依赖不抛出的析构函数。
  • @JonathanWakely 哈,对!没有过多考虑自定义删除器。
  • 另外,删除托管对象一定不能抛出异常,参见[unique.ptr.single.modifiers]/3
  • @Xeo 但它是否也依赖于不抛出的删除器?最后,在 Jonathan 的评论之后,这个标准引用似乎有点太笼统了,即使对于 "as if" 句来说也是如此。
【解决方案3】:

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

【讨论】:

  • @Ali 由于reset 没有任何先决条件,因此您始终可以调用它,因为任何移动都指定将移动的对象留在未定义但有效状态,这使得任何没有先决条件的函数的使用始终有效。只是在依赖某些先决条件时(例如指针为nullptr,但标准可能保证std::unique_ptr),这需要您首先检查相应的条件。移动不会以任何方式使对象失效,这是一种常见的误解。
  • @Ali 在任何移动之后,保证对象处于有效但不确定的状态。 std::unique_ptr 进一步保证在被移出后它将是一个空指针。
  • @Ali:这太危险了,因为基本上你可以对返回值做的唯一安全的事情就是把它放回另一个unique_ptr&lt;T, Del&gt;。几乎任何其他事情都会导致诸如删除器不匹配、内存泄漏或双重删除之类的事情。
  • 如果删除者不匹配第一个不只是警告,它会拒绝编译。这是一件好事
  • 另外,我相信第一个会移动必要的删除器,而第二个不会改变任何删除器状态。
猜你喜欢
  • 2014-02-27
  • 2021-07-04
  • 2011-10-16
  • 1970-01-01
  • 2020-06-22
  • 2018-07-29
  • 1970-01-01
  • 1970-01-01
  • 2011-04-08
相关资源
最近更新 更多