std :: unique_ptr <t> :: reset的强异常保证

时间:2016-07-18 23:30:49

标签: c++ c++11

假设我有一个函数reset

template<typename T, typename... Args>
void reset(unique_ptr<T>& p, Args&&... args)
{
    // implementation (1)
    p.reset(new T(forward<Args>(args)...));

    // implementation (2)
    p = make_unique<T>(forward<Args>(args)...);
}

我是否正确:

  1. 对于实现(1),如果在销毁p的原始指针期间抛出异常,则new - 内存将被泄露;

  2. 对于实施(2),任何事情都不会泄露;

  3. 所以我们应该更喜欢(2)到(1)。

3 个答案:

答案 0 :(得分:4)

作为Jonathan Wakely points out,这一点没有实际意义,因为如果析构函数抛出,unique_ptr::reset的行为是不确定的。

如果析构函数抛出,两个版本都有UB,所以这不是优先选择其中一个的理由。

[unique.ptr.single.modifiers](标准草案)

  

3要求:表达式get_deleter()(get())应格式正确,行为应明确,不得抛出异常

即使行为定义明确......

(1)不会泄漏。 unique_ptr在破坏旧参数之前取得参数的所有权。

  

4效果:将p分配给存储的指针,然后如果存储的指针的旧值old_p不等于nullptr,则调用get_deleter()(old_p) 。 [注意:这些操作的顺序很重要,因为对get_deleter()的调用可能会破坏*this。 - 结束说明]

选择其中一个的原因

  • (1)只需要C ++ 11,(2)需要C ++ 14或make_unique自己的样板。
  • (2)没有明确调用new,所以使用旧的经验法则更容易推理内存整洁:&#34;每个新的&#34;删除一次。

答案 1 :(得分:3)

template<typename T, typename... Args>
void reset(unique_ptr<T>& p, Args&&... args)
{
  auto tmp = std::make_unique<T>(std::forward<Args>(args)...);
  std::swap( tmp, p );
}

正如另一个答案所指出的,reset要求析构函数不抛出。

然而,上述内容并没有提出这样的要求。 swap永远不应该抛出。如果make_unique中的ctor抛出,则会发生明显的事情。 tmp的ctor不会抛出。

如果tmp的dtor抛出,它已包含p包含的内容,p已包含新数据。事情处于可预测的状态。

这不是强大的异常保证,因为如果发生抛出(在p中的内容被破坏期间),事物不会回滚到原始状态。我认为没有合乎逻辑的方法可以提供这种保证:如果clone的{​​{1}}不是这样的话?但即便如此,如果T抛出,任何临时对象也会抛出,所以事情并不实际。

答案 2 :(得分:3)

所有~unique_ptrunique_ptr::operator=unique_ptr::reset都是noexcept。因此,从unique_ptr拥有的对象的析构函数中抛出异常将始终生成std::terminate。这使得内存泄漏在两种情况下都无关紧要。

Here解释了为什么make_shared / make_unique优于unique_ptr(new ...) / shared_ptr(new ...)。这就是为什么我们应该更喜欢(2)到(1)

请勿忘记:您无法使用make_shared / make_unique指定自定义删除者。