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

时间:2012-01-13 21:37:39

标签: c++ exception

我有一些代码在创建对象后添加到std::vectorstd::map

v.push_back(object);     // std::vector
m[object->id] = object;  // std::map

我想让它具有强大的异常保证。通常,为了使这些操作成为原子,我会为每个容器实现一个swap方法,并调用所有可能抛出容器临时副本的函数:

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)

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

4 个答案:

答案 0 :(得分:4)

一般案例

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

  1. 复制矢量
  2. 更新副本
  3. 更新地图
  4. 交换副本和原始载体
  5. 另一种选择是追回和重新抛出:

      v.push_back(object);
      try
      {
        m.insert(object->id, object); // Assuming it cannot be present yet
      }
      catch(..)
      {
        v.pop_back();
        throw;
      }
    

    或者相反。我选择了此订单,因为vector::pop_back()保证不会失败。

    更新:如果可能存在object-> id,请参阅Grizzly's answer了解解决方案。


    指针的具体案例

    但是,当您使用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);
    

答案 1 :(得分:4)

我认为这种情况下使用try-catch是处理它的正确方式。如果访问map,则撤消vector上的操作并重新抛出:

v.push_back(object);  
try 
{
  m[object->id] = object;
} 
catch(...)
{
  v.pop_back();
  throw;
}

但是这仍然不能给你一个强有力的保证,因为operator[] 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;
}

答案 2 :(得分:4)

您可以使用在销毁时回滚操作的scopeguard对象,除非被告知不要。 Generic: Change the Way You Write Exception-Safe Code — Forever中列出了这种方法。

E.g。类似的东西:

container1.push_back(a);
Guard g(bind(&ContainerType::pop_back, &container1));
container2.push_back(a);
// ...
g.dismiss();

答案 3 :(得分:2)

我想你可以推出自己的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();

(我选择反转矢量推送,因为它总是可逆的,而使用地图你可能会覆盖另一个你必须尝试回来的元素。)