C++ 全有或全无的类似事务的模式
C++ Transaction-like Pattern for All or Nothing Work
假设我有两个函数DoTaskA
和DoTaskB
——都能够抛出TaskException
——以及它们对应的"回滚"函数UndoTaskA
和UndoTaskB
。 使用哪种最佳模式才能使两者都成功或两者都失败?
我现在拥有的最好的是
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;
}
我知道is_task_b_done
是不必要的,但如果我们稍后添加第三个或第四个任务,展示代码对称性可能会很好。
不喜欢这段代码,因为辅助布尔变量。 也许新的 C++11 中有一些我不知道的东西,可以更好地编码它?
一点 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;
}
}
附言。正如Anon Mail所说,如果你有很多taskX对象,最好将所有这些taskX对象推送到一个容器中,为容器提供相同的语义(在容器上调用提交以让它提交每个拥有的守护对象)。
.PPS。原则上,您可以在 RAII dtor 中使用std::uncaught_exception
,而不是显式提交。 我更喜欢在这里明确提交,因为我认为它更清晰,并且如果您以return FAILURE_CODE
而不是异常提前退出范围,也可以正常工作。
在C++中实现事务一致性。在Dr. Dobb的日记中,有一个很好的方法使用ScopeGuard模式进行了描述。这种方法的优点在于,这在正常情况和异常情况下都需要清理。它利用了这样一个事实,即确保对象析构函数调用任何范围出口,而异常情况只是另一个范围出口。
你有没有想过 CommandPattern?命令模式说明
你封装了执行DoTaskA()操作所需的所有数据在命令类的对象中,具有奖励,您可以反转所有这些都,如果需要(因此,如果失败,则无需进行特殊撤消执行)。命令模式特别适合处理"全有或全无"情况。
如果您有多个相互构建的命令,例如您的示例可以阅读,那么你应该调查责任链
也许反应堆模式可能会派上用场(反应堆描述在这里)这将反转控制流,但它感觉很自然并且具有将您的系统转变为强大的多线程、多组件的优势设计。但这里可能有点矫枉过正,很难从例子中分辨出来。
实现此目的的最佳方法是使用作用域防护,基本上是一个小型 RAII 习惯用法,如果引发异常,它将调用回滚处理程序。
不久前,我询问了ScopeGuard的简单实现,这个问题演变成我在生产项目中使用的一个很好的实现。它与 c++11 和 lambda 一起使用作为回滚处理程序。
我的源代码实际上有两个版本:一个版本在构造函数处理程序抛出时将调用回滚处理程序,另一个版本在发生这种情况时不会抛出。
在此处查看源代码和使用示例。
对于可伸缩性,您希望保存需要对容器中的任务执行撤消的事实。 然后,在 catch 块中,您只需调用容器中记录的所有撤消。
例如,容器可以包含函数对象,以撤消已成功完成的任务。
- 具有奇怪重复模板模式的派生类中的成员变量已损坏
- 为什么在保护模式下继承升级不起作用
- 如何在全屏模式下(在OpenGL中)使背景透明
- 为什么使用__LINE_的代码在发布模式下在MSVC下编译,而不是在调试模式下
- 派生类是否可以在抽象工厂设计模式中具有数据成员
- 此模式的C++RegEx
- avrogencpp能为模式中的每种类型生成单独的头文件吗
- 使用可变模板的Broadcaster/Listener模式
- c++方法参数只能在linux的发布模式下自行更改
- 资源管理设计模式
- 使用 mod_gsoap 部署服务时,如何在 Gsoap 中更改 soap 上下文的模式?
- C++ 无法在字符数组中使用 for 循环打印字母模式
- 小字符串优化(调试与发布模式)
- 可视化C++:发布模式的运行时库作为'Multi-threaded Debug DLL'
- 如何设计具有不同类型的通知和观察器的观察者模式?
- 在C++的一系列数字中查找重复模式
- 是否允许使用带有"w+"模式的 freopen 进行标准设置?
- C++ 使用存储在动态数组中的文本文件中的数据查找模式
- C++Poco ODBC事务-自动提交模式
- C++ 全有或全无的类似事务的模式