请考虑以下代码。
#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;
和notify_observers();
。在这种情况下,ItemsContainer::enable
可能会破坏容器的内部状态。
这不是我第一次遇到这种情况。 是否有最佳实践规则?
注意:我将代码示例减少到最小,但事实上,Item::enable
是一个setter:Item::set_enabled(bool)
,我想为ItemsContainer
实现相同的功能。这意味着如果启用失败,则可以禁用项目,但在此处再次,不指定异常保证。
答案 0 :(得分:2)
如果你所依赖的图书馆在特殊情况下没有给你行为保证,那么你就不能给予依赖行为保证。如果它为您提供了基本保证,您可以将其用于某一点,但可能成本非常高。
在您的情况下,例如,您希望维护所有项都启用或不启用的不变量。所以你的选择是
setEnabledWithTransaction
。 Item
是否可以复制?如果复制了一个项目,然后丢弃了该副本,或者修改了旧对象,对观察者意味着什么?答案 1 :(得分:1)
这似乎更像是应用程序选择而不是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
返回值表示成功。或者,您可以返回成功启用的项目数。如果失败的可能性非常小,那么您应该这样做。如果失败的可能性非常小,或者如果失败表明系统出现故障,那么你应该扔掉。