[英]Exception-safe for loop
考慮下面的代碼。
#include <cassert>
#include <stdexcept>
#include <vector>
struct Item {
Item() : m_enabled(false) {}
void enable()
{
m_enabled = true;
notify_observers(); // can throw
}
bool enabled() const {return m_enabled;}
private:
bool m_enabled;
};
struct ItemsContainer {
ItemsContainer() : m_enabled(false) {}
void enable()
{
bool error = false;
for(Item & item : m_items) {
try {
item.enable();
} catch(...) {
error = true;
}
}
m_enabled = true;
if(error) throw std::runtime_error("Failed to enable all items.");
}
bool enabled() const
{
for(Item const & item : m_items) assert(item.enabled() == m_enabled);
return m_enabled;
}
private:
std::vector<Item> m_items;
bool m_enabled;
};
在我的情況下, Item
的實現如圖所示(我無法更改),並且我正在嘗試實現ItemsContainer
。 我不完全知道如何處理異常。
在建議的實現中,啟用容器時,即使通知觀察者其中一個拋出異常,我們也會啟用所有項目,最后,我們有可能拋出異常以通知調用方(至少)存在問題觀察者之一。 但是,容器的狀態已修改。 這是違反直覺的嗎? 我是否應該通過在發生故障的情況下禁用已啟用的項目並保持初始狀態不變來嘗試為ItemsContainer::enable
賦予強烈的異常保證?
在ItemsContainer::enabled
,斷言是否合理? 由於我沒有對Item
的控制,並且沒有記錄任何例外保證,因此可能會發生m_enabled = true;
指令m_enabled = true;
和notify_observers();
被交換。 在這種情況下, ItemsContainer::enable
可能會破壞容器的內部狀態。
我已經不是第一次遇到這種情況了。 有最佳實踐規則嗎?
注意: 我將代碼示例減至最少,但實際上, Item::enable
是一個setter: Item::set_enabled(bool)
,我想為ItemsContainer
實現相同的功能。 這意味着,如果啟用失敗,則可以禁用項目,但是在此情況下,無需指定異常保證。
如果您所依賴的庫在特殊情況下沒有給您行為保證,那么您就不能提供從屬行為保證。 如果它為您提供了基本保證,則可以在一定程度上使用該保證,但成本可能很高。
例如,在您的情況下,您想要保持不變,即所有項均已啟用,或者都不啟用。 所以你的選擇是
setEnabledWithTransaction
。 Item
復制嗎? 如果復制了一個項目,但是副本被丟棄,或者修改了舊對象,對觀察者意味着什么? 嗯,這似乎更像是應用程序的選擇,而不是API的選擇。 根據您的應用程序,您可能需要執行以下任一操作:
Item
保持當前狀態 Item
重置為先前狀態(事務類型保護),然后拋出 如果您認為用戶可能需要多個用戶,則可以輕松地將其設計到API中:
bool setEnabled(bool enable, bool failFast = false)
{
bool error = false;
for(Item & item : m_items) {
try {
enable ? item.enable() : item.disable();
} catch(...) {
error = true;
if(failFast)
throw std::runtime_error("Failed to enable all items.");
}
}
m_enabled = true;
return error;
}
bool setEnabledWithTransaction(bool enable)
{
bool error = false;
vector<Item> clone(m_items);
for(Item & item : m_items) {
try {
enable ? item.enable() : item.disable();
} catch(...) {
// use the saved state (clone) to revert to pre-operation state.
}
}
m_enabled = true;
return error;
}
請注意, bool
返回值指示成功。 或者,您可以返回成功啟用的項目數。 如果失敗的機會微不足道,這就是您應該采取的措施。 如果發生故障的機會非常小,或者如果發生故障表明系統出現故障,則應該拋出該故障。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.