小さなRAIIコミット/ロールバックスコープガードは次のようになります。
#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();
}
};
したがって、トランザクションが成功した後は常にガードオブジェクトを作成し、すべてのトランザクションが成功したcommit
後にのみ呼び出すと想定しています。
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。Anon Mailが言うように、 taskXオブジェクトが多数ある場合は、それらすべてをコンテナーにプッシュして、コンテナーに同じセマンティクスを与えることをお勧めします(コンテナーでcommitを呼び出して、所有する各ガードオブジェクトをコミットします)。
PPS。std::uncaught_exception
原則として、明示的にコミットする代わりに、RAIIdtorで使用できます。return FAILURE_CODE
ここで明示的にコミットすることをお勧めします。これは、より明確であり、例外ではなく早期にスコープを終了した場合にも正しく機能すると思うためです。