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