异常安全的 for 循环
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;
和notify_observers();
指令。在这种情况下,ItemsContainer::enable
可能会破坏容器的内部状态。
这不是我第一次遇到这种情况。是否有最佳实践规则?
注意:我将代码示例减少到最低限度,但实际上,Item::enable
是一个二传手: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
返回值表示成功。或者,您可以返回成功启用的项目数。如果失败的机会不平凡,您应该这样做。如果发生故障的可能性非常小,或者如果故障表明某些东西是系统故障,那么您应该抛出。
- 如何在C++中从两个单独的for循环中添加两个数组
- 为什么我的for循环不能正确获取argv
- 在基于范围的for循环中使用结构化绑定声明
- 通过for循环使用用户输入填充列表
- 使用for循环检查数组中的重复项
- 在for循环中使用auto vs decltype(vec.size())来处理字符串的向量
- 为什么 const std::p air<K,V>& 在 std::map 上基于范围的 for 循环不起作用?
- 正在使用for循环创建QScatterSerie
- Python中的for循环与C++有何不同
- 在更改for循环的第三部分后,未使用for循环结果
- 在 for 循环中查找问题时遇到困难
- 嵌套for循环C++的问题(初学者)
- 如何用for循环在c++中生成单词三角形
- 如何在for循环中包含两个索引值的测试条件
- 带有多个独立参数的C++For循环
- C++ Python 循环"for i, num in enumerate(list):"版本
- C 多循环 for () 基础知识
- 没有条件值的 FOR 循环"for (int i = 1; ; i++)"无法正常工作
- 为用户提供循环for循环的选项
- 打破循环for循环