[英]Strong exception guarantee for std::unique_ptr<T>::reset
假設我有一個功能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)。
正如Jonathan Wakely 指出的那樣 ,這一點沒有實際意義,因為如果析構函數拋出,則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
,因此使用舊的經驗法則更容易推理內存整潔:“每個新刪除一個”。 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拋出, make_unique
發生明顯的事情。 tmp
的ctor不會拋出。
如果tmp
的dtor拋出,它已經包含p
包含的內容,並且p
已經包含新數據。 所以事情處於可預測的狀態。
這不是強大的異常保證,因為如果發生拋出(在破壞p
的內容期間),事物不會回滾到原始狀態。 我認為沒有合理的方法來提供這種保證:也許如果clone
T
不是嗎? 但即便如此,如果p
拋出,任何臨時對象也會拋出,所以事情並不真實。
所有~unique_ptr
, unique_ptr::operator=
和unique_ptr::reset
都是noexcept
。 因此,從unique_ptr
擁有的對象的析構函數拋出異常將始終導致std::terminate
。 這使得內存泄漏在兩種情況下都無關緊要。
這里解釋了為什么make_shared
/ make_unique
比unique_ptr(new ...)
/ shared_ptr(new ...)
更可取。 這就是為什么我們應該更喜歡(2)到(1)
不要忘記:您無法使用make_shared
/ make_unique
指定自定義刪除make_unique
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.