假设我有一个函数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),如果在销毁p
的原始指针期间抛出异常,则new
- 内存将被泄露;
对于实施(2),任何事情都不会泄露;
所以我们应该更喜欢(2)到(1)。
答案 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
。 - 结束说明]
选择其中一个的原因
make_unique
自己的样板。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_ptr
,unique_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
指定自定义删除者。