0
votes

I have an STL container and I need to perform an action on each element in the container. But if the action fails on any element, I want to reverse the action on any elements that have already been changed.

For example, if I had an STL vector with pointers to a number bankAccount classes and wanted to increase each one by $50. But if any of the bank accounts fail to increase by 50, I want to cancel the increase entirely and decrease by $50 any of the accounts that have already been increased.

std::vector<bankAccount*> bankAccounts;
std::vector<bankAccount*>::iterator iter;

for (iter = bankAccounts.begin(); iter != bankAccounts.end(); ++iter)
{
    try
    {
        iter->increaseBalance(50);
    }
    catch (...)
    {
        // One of the bankAccounts failed to increase by 50, now I need to go 
        // back and decrease by 50 all of the bankAccounts that have already 
        // been increased.
    }
}

Is there any elegant way to do this? Maybe with STL algorithms or using reverse iterators?

2
write a functor and use for_each - Ramadheer Singh
Is it possible to loop through and just check if the operation will succeed? Then you just make sure they'll all succeed, and if so, perform the action. - GManNickG
You should catch the specific exception that you expect might be thrown. If you have a catch (...) block, you cannot know what exception was thrown and the only sensible ways to leave such a catch block are either to rethrow the exception or to terminate the application. - James McNellis

2 Answers

9
votes

Here's what I would do:

  • Move the try/catch outside the loop
  • Create a duplicate of the bankAccounts container
  • Iterate over the duplicate container, calling increaseBalance on each item
  • If the loop sucessfully completed, swap() the original and the duplicate container

The code would look something like this:

std::vector<bankAccount> bankAccounts;
...
std::vector<bankAccount> tmp(bankAccounts);

try
{
   for (iter = tmp.begin(); iter != tmp.end(); ++iter)
   {
     iter->increaseBalance(50);
   }
   bankAccounts.swap(tmp);
}
catch (...)
{
}

Please note that holding a pointer to an object inside a std::vector is generally not that good an idea as the container expects the data stored in it to have value semantics, not pointer semantics. This can lead to dangling pointers, memory leaks and also requires additional cleanup code that you don't need otherwise (to delete the items in container manually). With the code above, I've switched to holding the data inside the vector, if that's not an option you need to ensure that you're using a manual deep copy when you're copying the vector.

Actually, you can reduce the code to the following if you assume the same definitions for bankAccounts and tmp:

std::for_each(tmp.begin(), tmp.end(),
              std::mem_fun_ref(&bankAccount::increaseBalance, 50));
bankAccounts.swap(tmp);

The main advantage of the code above is that in both cases, it is exception safe without any further special handling.

1
votes

I think a more elegant approach would to treat the operation as a transaction. In other words, create a replacement copy of the accounts, and overwrite the original on success.