I have some code that adds to a std::vector and a std::map after creating an object.
v.push_back(object);     // std::vector
m[object->id] = object;  // std::map
I want to make this have a strong exception guarantee. Normally, to make operations like these atomic, I would implement a swap method for each container, and call all of the functions that could throw on temporary copies of the container:
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)
However, making temporary copies of the entire vector and map seems very expensive. Is there any way to implement a strong exception guarantee for this function without the expensive copies?
Technically, only one copy is required:
Another option is catch-roll-back-and-rethrow:
  v.push_back(object);
  try
  {
    m.insert(object->id, object); // Assuming it cannot be present yet
  }
  catch(..)
  {
    v.pop_back();
    throw;
  }
Or the other way around. I picked this order because the vector::pop_back() is guaranteed not to fail.
UPDATE: If object->id could be present, see Grizzly's answer for a solution.
However, as you are using object->, you might be storing pointers. The copy-constructor of a pointer cannot throw, and we can use that fact to simplify the code:
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
And if you are really worried about frequent resizing:
if (v.capacity() == v.size()) v.resize(v.size()*2); // or any other growth strategy
m[object->id] = object;
v.push_back(object);
                        I think this is a situation where using try-catch is the correct manner of handling it. If the access to the map throws you undo the operation on the vector and rethrow:
v.push_back(object);  
try 
{
  m[object->id] = object;
} 
catch(...)
{
  v.pop_back();
  throw;
}
However this will still not give you a strong guarantee, since operator[] on maps is a problematic instruction for exception safety (if the element is not in the map an object is default constructed, which will stay in the map, if the operator= throws (very unlikely in this case, since you seem to be storing pointers, but still).
Therefore I would rewrite it as
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;
}
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With