添加到多个 std 容器时C++异常安全

Exception safety in C++ when adding to multiple std containers

本文关键字:C++ 异常 安全 std 添加      更新时间:2023-10-16

我有一些代码可以在创建对象后添加到std::vectorstd::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)

但是,制作整个矢量和地图的临时副本似乎非常昂贵。有没有办法在没有昂贵副本的情况下为此功能实现强大的异常保证?

一般情况

从技术上讲,只需要一份副本:

  1. 复制矢量
  2. 更新副本
  3. 更新地图
  4. 交换副本和原始矢量

另一种选择是捕获-回滚和重新抛出:

  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();

(我选择反转矢量推送,因为它总是可逆的,而对于地图,你可能已经覆盖了另一个元素,你必须尝试返回。