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

Kai*_*Kai 12 c++ exception

我有一些代码在创建对象后添加到a std::vector和a std::map.

v.push_back(object);     // std::vector
m[object->id] = object;  // std::map
Run Code Online (Sandbox Code Playgroud)

我想让它有一个强大的异常保证.通常,为了使这些操作成为原子,我会为每个容器实现一个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)
Run Code Online (Sandbox Code Playgroud)

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

Sjo*_*erd 4

一般案例

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

  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;
  }
Run Code Online (Sandbox Code Playgroud)

或者反过来。我选择这个订单是因为vector::pop_back()保证不会失败。

更新:如果 object->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
Run Code Online (Sandbox Code Playgroud)

如果您真的担心频繁调整大小:

if (v.capacity() == v.size()) v.resize(v.size()*2); // or any other growth strategy
m[object->id] = object;
v.push_back(object);
Run Code Online (Sandbox Code Playgroud)