添加到多个 std 容器时C++异常安全
Exception safety in C++ when adding to multiple std containers
我有一些代码可以在创建对象后添加到std::vector
和std::map
。
v.push_back(object); // std::vector
m[object->id] = object; // std::map
我想让它有一个强大的例外保证。通常,为了使这些操作原子化,我将为每个容器实现一个交换方法,并调用可以在容器的临时副本上抛出的所有函数:
vector temp_v(v);
map temp_map(m);
temp_v.push_back(object);
temp_m[object->id] = object;
// The swap operations are no-throw
swap(temp_v, v)
swap(temp_m, m)
但是,制作整个矢量和地图的临时副本似乎非常昂贵。有没有办法在没有昂贵副本的情况下为此功能实现强大的异常保证?
一般情况
从技术上讲,只需要一份副本:
- 复制矢量
- 更新副本
- 更新地图
- 交换副本和原始矢量
另一种选择是捕获-回滚和重新抛出:
v.push_back(object);
try
{
m.insert(object->id, object); // Assuming it cannot be present yet
}
catch(..)
{
v.pop_back();
throw;
}
或者反过来。我选择这个订单是因为vector::pop_back()
保证不会失败。
更新:如果对象>id可能存在,请参阅Grizzly的答案以获取解决方案。
指针的特定案例
但是,当您使用 object->
时,您可能会存储指针。指针的复制构造函数不能抛出,我们可以利用这一事实来简化代码:
v.reserve(v.size() + 1);
m[object->id] = object; // can throw, but not during the assignment
v.push_back(object); // will not throw: capacity is available, copy constructor does not throw
如果您真的担心频繁调整大小:
if (v.capacity() == v.size()) v.resize(v.size()*2); // or any other growth strategy
m[object->id] = object;
v.push_back(object);
我认为在这种情况下,
使用 try-catch 是正确的处理方式。如果对map
的访问引发,请撤消对vector
的操作并重新引发:
v.push_back(object);
try
{
m[object->id] = object;
}
catch(...)
{
v.pop_back();
throw;
}
然而,这仍然不能给你一个强有力的保证,因为operator[]
on maps
是一个有问题的异常安全指令(如果元素不在默认构造对象的map
,如果operator=
抛出,它将保留在映射中(在这种情况下不太可能,因为你似乎在存储指针, 但仍然(。
因此,我会将其重写为
try
{
auto iter = m.find(object->id);//used auto for shorter code,
if(iter == m.end())
m.insert(std::make_pair(object->id, object);
else
iter->second = object;
}
您可以使用 scopeguard 对象,该对象在销毁时回滚操作,除非被告知不要这样做。通用:永远更改编写异常安全代码的方式中概述了此方法。
例如,像这样:
container1.push_back(a);
Guard g(bind(&ContainerType::pop_back, &container1));
container2.push_back(a);
// ...
g.dismiss();
我想
你可以滚动自己的RAII类型对象:
template<typename T>
class reversible_vector_pusher
{
private:
std::vector<T> * const ptrV;
bool committed = false;
public:
reversible_vector_pusher(std::vector<T> & v, const T & obj) : ptrV(&v)
{ v.push_back(obj); }
void commit()
{ committed = true; }
~reversible_vector_pusher()
{
if(! committed)
ptrV->pop_back();
}
};
reversible_vector_pusher<...> rvp(v, object); // replace ... with object's type
m[object->id] = object;
rvp.commit();
(我选择反转矢量推送,因为它总是可逆的,而对于地图,你可能已经覆盖了另一个元素,你必须尝试返回。
相关文章:
- 处理多个异常集合的C++方法
- 我在c++代码中生成了一个运行时#3异常
- 孤立代码块在结构中引发异常
- C++中的赋值发生,尽管右侧出现异常
- 从构造函数抛出异常时如何克服内存泄漏
- 异常属于C++中的线程还是进程
- 当类定义不可见时捕获异常
- 引发异常:读取访问冲突**dynamicArray**为0x1118235.发生
- 为什么异常不退出程序?
- 为什么我应该在异常处理中使用std::cerr而不是std::cout
- 如何修复链表类实现的未处理异常0xDDDDDDDD
- 关于:C++中异常对象的范围:为什么我没有得到副本?
- 是什么导致了Unity 3D中的"错误线程异常"?
- 如何将strftime中的格式错误作为异常捕获
- 创建具有 new in 函数和"this is nullptr"异常的对象
- 尝试使用智能指针时引发异常
- 函数如何通知用户它基于函数原型抛出异常?
- 是否值得降低我的代码的可读性,以便在出现内存不足错误时提供异常安全性?
- 当我使用 C++ 中的 C# dll 来使用 Selenium 时,存在异常处理问题
- Pytorch torch.cholesky忽略异常