std::unique_ptr reset() 操作顺序

Rup*_*rrt 10 c++ unique-ptr

呼唤void reset( pointer ptr = pointer() ) noexcept; 会调用以下操作

\n
\n

给定 current_ptr(由 *this 管理的指针),按以下顺序执行以下操作:

\n
    \n
  1. 保存当前指针的副本 old_ptr = current_ptr
  2. \n
  3. 使用参数 current_ptr = ptr 覆盖当前指针
  4. \n
  5. 如果旧指针非空,则删除先前管理的对象 if(old_ptr) get_deleter()(old_ptr)。
  6. \n
\n
\n

参考参数

\n

这个特殊命令的原因是什么?为什么不先执行 3),然后再执行 2)?在这个问题中std::unique_ptr::reset 检查托管指针无效?第一个答案引用了标准

\n
\n

[\xe2\x80\xa6] [ 注意:这些操作的顺序很重要,因为调用 get_deleter() 可能会破坏 *this。\xe2\x80\x94结束注]

\n
\n

这是唯一的原因吗?怎么能get_deleter()毁掉unique_ptr*this )呢?

\n

Use*_*ess 3

在分析规定的步骤顺序时,考虑哪些步骤可能会抛出以及什么状态会使所有内容保持不变,这通常很有用 - 目的是我们永远不会陷入无法恢复的情况。

\n

请注意,从这里的文档中可以看出:

\n
\n

不同的是std::shared_ptr,可以通过任何满足NullablePointer 的std::unique_ptr自定义句柄类型来管理对象。例如,这允许通过提供定义; 来管理位于共享内存中的对象。或另一个奇特的指针。Deletertypedef boost::offset_ptr pointer

\n
\n

因此,按照当前的顺序:

\n
    \n
  1. 保存当前指针的副本 old_ptr = current_ptr

    \n

    如果花式指针的复制构造函数抛出异常,unique_ptr 仍然拥有原始对象,并且新对象不被拥有:OK

    \n
  2. \n
  3. 使用参数 current_ptr = ptr 覆盖当前指针

    \n

    如果花式指针的复制赋值抛出异常,unique_ptr 仍然拥有原始对象,并且新对象不被拥有:OK

    \n

    (这假设花式指针的复制赋值运算符满足通常的异常安全保证,但如果没有它,unique_ptr 就无能为力)

    \n
  4. \n
  5. 如果旧指针非空,则删除先前管理的对象 if(old_ptr) get_deleter()(old_ptr)

    \n

    在此阶段,unique_ptr 拥有新对象,并且可以安全地删除旧对象。

    \n
  6. \n
\n

换句话说,两种可能的结果是:

\n
std::unique_ptr<T, FancyDeleter> p = original_value();\ntry {\n  auto tmp = new_contents();\n  p.reset(tmp);\n  // success\n}\ncatch (...) {\n  // p is unchanged, and I\'m responsible for cleaning up tmp\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在您建议的订单中:

\n
    \n
  1. 如果原指针非空,则将其删除

    \n

    在此阶段 unique_ptr 无效:它已提交了不可逆的更改(删除),如果下一步失败,则无法恢复良好状态

    \n
  2. \n
  3. 使用参数 current_ptr = ptr 覆盖当前指针

    \n

    如果花式指针的复制赋值抛出异常,我们的 unique_ptr 将变得不可用:存储的指针是不确定的,我们无法恢复旧的指针

    \n
  4. \n
\n

换句话说,我所说的不可恢复的情况如下所示:

\n
std::unique_ptr<T, FancyDeleter> p = original_value();\ntry {\n  auto tmp = new_contents();\n  p.reset(tmp);\n  // success\n}\ncatch (...) {\n  // I can clean up tmp, but can\'t do anything to fix p\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在该异常之后,p甚至无法安全地销毁,因为在其内部指针上调用删除器的结果可能是双重释放。

\n
\n

注意。删除器本身不允许抛出异常,所以我们不必担心这一点。

\n

注释上写着

\n
\n

... 的召唤get_\xc2\xaddeleter()可能会破坏*this

\n
\n

听起来不对,调用get_\xc2\xaddeleter()(old_\xc2\xadp)确实可能... if*old_p是一个包含其自身的 unique_ptr 的对象。在这种情况下,删除器调用必须放在最后,因为实际上您无法对其之后的 unique_ptr 实例安全地执行任何操作。

\n

尽管这个极端情况是将删除器调用放在最后的一个坚实原因,但我觉得强异常安全论点可能不太人为(尽管具有指向自身的唯一指针的对象是否比带有抛出赋值的花哨指针更常见或不常见)任何人的猜测)。

\n