C++类似事务的模式,适用于所有或没有任何工作

kir*_*kun 10 c++ design-patterns transactional

假设我有两个函数DoTaskADoTaskB-both能够抛出TaskException相应的"回滚"函数UndoTaskAUndoTaskB.什么是最好的模式,以便成功或两者都失败?

我现在最好的是

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;
}
Run Code Online (Sandbox Code Playgroud)

我知道这is_task_b_done是不必要的,但是如果我们稍后添加第三个或第四个任务,则可能显示代码对称性.

由于辅助布尔变量,不喜欢这段代码.也许新的C++ 11中有一些我不知道的东西,可以更好地编写代码吗?

Use*_*ess 13

一个小的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();
    }
};
Run Code Online (Sandbox Code Playgroud)

因此,我们假设在事务成功后我们将始终创建保护对象,并且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;
    }
}
Run Code Online (Sandbox Code Playgroud)

PS.正如Anon Mail所说,最好将所有这些taskX对象推送到容器中,如果你有很多,给容器提供相同的语义(在容器上调用commit以让它提交每个拥有的guard对象).


PPS.原则上,您可以std::uncaught_exception在RAII dtor中使用而不是显式提交.我更喜欢在这里明确提交,因为我认为它更清晰,并且如果您提前退出范围return FAILURE_CODE而不是异常,也可以正常工作.


Per*_*est 7

在C++中很难实现事务一致性.在Dr Dobb的期刊中使用ScopeGuard模式描述了一种很好的方法.该方法的优点在于它在正常情况和异常情况下都需要清理.它利用了以下事实:确保对象析构函数调用任何作用域出口,而异常情况只是另一个作用域出口.

  • 也许您可以内联方式复制(或绘制)代码,以防万一有人读这个答案的时候Dobbs博士倒下了? (2认同)