C++ Transaction-like Pattern for All or Nothing Work

c++, design-patterns, transactional

Solution

A little RAII commit/rollback scope guard might look like this:

#include <utility>
#include <functional>

class CommitOrRollback
{
    bool committed;
    std::function<void()> rollback;

public:
    CommitOrRollback(std::function<void()> &&fail_handler)
        : committed(false),
          rollback(std::move(fail_handler))
    {
    }

    void commit() noexcept { committed = true; }

    ~CommitOrRollback()
    {
        if (!committed)
            rollback();
    }
};

So, we're assuming we'll always create the guard object after the transaction succeeds, and call `commit` only after all the transactions have succeeded.

void complicated_task_a();
void complicated_task_b();

void rollback_a();
void rollback_b();

int main()
{
    try {
        complicated_task_a();
        // if this ^ throws, assume there is nothing to roll back
        // ie, complicated_task_a is internally exception safe
        CommitOrRollback taskA(rollback_a);

        complicated_task_b();
        // if this ^ throws however, taskA will be destroyed and the
        // destructor will invoke rollback_a
        CommitOrRollback taskB(rollback_b);


        // now we're done with everything that could throw, commit all
        taskA.commit();
        taskB.commit();

        // when taskA and taskB go out of scope now, they won't roll back
        return 0;
    } catch(...) {
        return 1;
    }
}

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_exception` in 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 a `return FAILURE_CODE` instead of an exception.

Problem

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?

Original source

Related problems