簡體   English   中英

異常安全的循環

[英]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 我不完全知道如何處理異常。

  1. 在建議的實現中,啟用容器時,即使通知觀察者其中一個拋出異常,我們也會啟用所有項目,最后,我們有可能拋出異常以通知調用方(至少)存在問題觀察者之一。 但是,容器的狀態已修改。 這是違反直覺的嗎? 我是否應該通過在發生故障的情況下禁用已啟用的項目並保持初始狀態不變來嘗試為ItemsContainer::enable賦予強烈的異常保證?

  2. ItemsContainer::enabled ,斷言是否合理? 由於我沒有對Item的控制,並且沒有記錄任何例外保證,因此可能會發生m_enabled = true;指令m_enabled = true; notify_observers(); 被交換。 在這種情況下, ItemsContainer::enable可能會破壞容器的內部狀態。

我已經不是第一次遇到這種情況了。 有最佳實踐規則嗎?

注意: 我將代碼示例減至最少,但實際上, Item::enable是一個setter: Item::set_enabled(bool) ,我想為ItemsContainer實現相同的功能。 這意味着,如果啟用失敗,則可以禁用項目,但是在此情況下,無需指定異常保證。

如果您所依賴的庫在特殊情況下沒有給您行為保證,那么您就不能提供從屬行為保證。 如果它為您提供了基本保證,則可以在一定程度上使用該保證,但成本可能很高。

例如,在您的情況下,您想要保持不變,即所有項均已啟用,或者都不啟用。 所以你的選擇是

  1. 如果任何使能函數引發異常,請繼續進行。 僅當您知道異常后項目的狀態時,此方法才有效,但您說這沒有記錄,因此該選項已退出。 即使已將其記錄在案,您是否可以確定觀察員已准備好處理此問題? 如果單個項目的多個觀察者中的第一個拋出異常,那是什么意思?其他觀察者是否被召喚? 如果啟用了一項但觀察者卻不知道,則整個應用程序會進入無效狀態嗎?
  2. 當引發異常時,請返回啟用的項目並再次禁用它們。 但這又有可能再次拋出異常嗎? 是否需要將這些額外的更改告知觀察者? 難道這又不會產生另一個例外嗎?
  3. 做類似Dennis的setEnabledWithTransaction Item復制嗎? 如果復制了一個項目,但是副本被丟棄,或者修改了舊對象,對觀察者意味着什么?
  4. 僅保留基本的異常保證,並在引發異常時清除整個向量。 這看起來似乎是激進的,但是如果您找不到使上述選項起作用的方法,則這是保持類不變的唯一方法,即所有項都已啟用或沒有。 如果您不知道商品的狀態並且無法安全地修改商品,則只能扔掉它們。
  5. 不要提供任何例外保證,並讓容器處於不一致狀態。 這當然是一個非常糟糕的選擇。 至少沒有提供基本異常保證的操作在使用異常的系統中沒有位置。

嗯,這似乎更像是應用程序的選擇,而不是API的選擇。 根據您的應用程序,您可能需要執行以下任一操作:

  1. 立即失敗,使所有Item保持當前狀態
  2. 失敗時將所有Item重置為先前狀態(事務類型保護),然后拋出
  3. 盡一切可能。

如果您認為用戶可能需要多個用戶,則可以輕松地將其設計到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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM