Suppose I have two functions DoTaskA and DoTaskB—both capable of throwing TaskException—with their corresponding “rollback” functions UndoTaskA and UndoTaskB. What is the best pattern to use so that either both succeed or both fail?
The best I have now is
bool is_task_a_done = false,
is_task_b_done = false;
try {
DoTaskA();
is_task_a_done = true;
DoTaskB();
is_task_b_done = true;
} catch (TaskException &e) {
// Before rethrowing, undo any partial work.
if (is_task_b_done) {
UndoTaskB();
}
if (is_task_a_done) {
UndoTaskA();
}
throw;
}
I know that is_task_b_done is unnecessary, but maybe good to show code symmetry in case we add a third or a fourth task later on.
Don’t like this code because of the auxiliary boolean variables. Perhaps there is something in the new C++11 that I’m not aware of, which can code this up more nicely?
A little RAII commit/rollback scope guard might look like this:
So, we’re assuming we’ll always create the guard object after the transaction succeeds, and call
commitonly after all the transactions have succeeded.PS. As Anon Mail says, it’s better to push all those taskX objects into a container if you have many of them, giving the container the same semantics (call commit on the container to have it commit each owned guard object).
PPS. In principle, you can use
std::uncaught_exceptionin the RAII dtor instead of explicitly committing. I prefer to explicitly commit here because I think it’s clearer, and also works correctly if you exit scope early with areturn FAILURE_CODEinstead of an exception.