[英]Exception-safe for loop
Consider the following code. 考虑下面的代码。
#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;
};
In my situation, Item
is implemented as shown (I cannot change it) and I am trying to implement ItemsContainer
. 在我的情况下,
Item
的实现如图所示(我无法更改),并且我正在尝试实现ItemsContainer
。 I don't exactly know how to deal with exceptions. 我不完全知道如何处理异常。
In the proposed implementation, when enabling the container, we enable all the items even if notifying the observers of one of them throws, and in the end, we potentially throw an exception to notify the caller that there was a problem with (at least) one of the observers. 在建议的实现中,启用容器时,即使通知观察者其中一个抛出异常,我们也会启用所有项目,最后,我们有可能抛出异常以通知调用方(至少)存在问题观察者之一。 However the state of the container is modified.
但是,容器的状态已修改。 Is it counter-intuitive?
这是违反直觉的吗? Should I try to give the strong exception guarantee to
ItemsContainer::enable
by disabling the already enabled items in case of failure and keeping initial state unchanged? 我是否应该通过在发生故障的情况下禁用已启用的项目并保持初始状态不变来尝试为
ItemsContainer::enable
赋予强烈的异常保证?
In ItemsContainer::enabled
, are the assertions reasonable? 在
ItemsContainer::enabled
,断言是否合理? Since I don't have the control on Item
and no exception guarantee is documented, it could occur that the instructions m_enabled = true;
由于我没有对
Item
的控制,并且没有记录任何例外保证,因此可能会发生m_enabled = true;
指令m_enabled = true;
and notify_observers();
和
notify_observers();
are swapped. 被交换。 In this case,
ItemsContainer::enable
could break the internal state of the container. 在这种情况下,
ItemsContainer::enable
可能会破坏容器的内部状态。
It is not the first time I face such a situation. 我已经不是第一次遇到这种情况了。 Are there best practice rules?
有最佳实践规则吗?
NB: I reduced the code sample to the minimum, but in fact, Item::enable
is a setter: Item::set_enabled(bool)
and I want to implement the same thing for ItemsContainer
. 注意: 我将代码示例减至最少,但实际上,
Item::enable
是一个setter: Item::set_enabled(bool)
,我想为ItemsContainer
实现相同的功能。 This means that items can be disabled if enabling fails, but here again, without specification of the exception guarantee. 这意味着,如果启用失败,则可以禁用项目,但是在此情况下,无需指定异常保证。
If the library you depend on does not give you behavior guarantees in exceptional cases, then you cannot give dependent behavior guarantees. 如果您所依赖的库在特殊情况下没有给您行为保证,那么您就不能提供从属行为保证。 If it gives you the basic guarantee, you can work with that to a point, but potentially at very high cost.
如果它为您提供了基本保证,则可以在一定程度上使用该保证,但成本可能很高。
In your case, for example, you want to maintain the invariant that either all items are enable, or none are. 例如,在您的情况下,您想要保持不变,即所有项均已启用,或者都不启用。 So your options are
所以你的选择是
setEnabledWithTransaction
. setEnabledWithTransaction
。 Is Item
copyable? Item
复制吗? What does it mean for the observers if an item is copied, but the copy then discarded, or the old object modified? Well this seems more like an application choice than an API choice. 嗯,这似乎更像是应用程序的选择,而不是API的选择。 Depending on your application you may want to do any of:
根据您的应用程序,您可能需要执行以下任一操作:
Item
s in their current state Item
保持当前状态 Item
s to previous state (transaction type protection) on failure then throw Item
重置为先前状态(事务类型保护),然后抛出 If you think that your users may want more than one of those then you could easily design it into the API: 如果您认为用户可能需要多个用户,则可以轻松地将其设计到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;
}
Note that the bool
return value indicated success. 请注意,
bool
返回值指示成功。 Alternatively you could return the number of successfully enabled items. 或者,您可以返回成功启用的项目数。 This is what you should do if the chance of a failure is non-trivial.
如果失败的机会微不足道,这就是您应该采取的措施。 If the chance of a failure is very very small, or if a failure indicates something that is a system failure then you should throw.
如果发生故障的机会非常小,或者如果发生故障表明系统出现故障,则应该抛出该故障。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.