Cだとgotoをつかうのが基本。C++だとtry/throw/catchもあり。
めんどくさいのが、atomicオペレーションを実装するときに、内部で副作用があちこちに発生して
途中で失敗したら取消てまわらないといけない場合。
正常系はこんなかんじだとして
Cのgotoパターン。
C++の例外処理をつかった例
おなじく例外処理をつかう別の例
C++0xで入るlambdaをつかった例
int
main(int ac, char **av)
{
int rc = 1;
int x = av[1] ? atoi(av[1]) : 0; /*atoiのエラーはみぬふり*/
f(x); /*エラーをおこすかも*/
g(x); /*エラーをおこすかも*/
return 0;
}
Cのgotoパターン。
#include <iostream>
#include <cstdlib>
using std::cout;
using std::endl;
int a = 0;
int b = 0;
void
show()
{
cout << "a=" << a << endl
<< "b=" << b << endl;
}
int
f(int x)
{
if (x > 100)
return -1;
a += x;
return 0;
}
void
undo_f(int x)
{
a -= x;
}
int
g(int x)
{
if (x > 10)
return -1;
b += x;
return 0;
}
int
main(int ac, char **av)
{
int rc = 1;
int x = av[1] ? atoi(av[1]) : 0;
if (f(x) < 0)
goto err_f;
if (g(x) < 0)
goto err_g;
rc = 0;
goto out;
err_g:
undo_f(x);
err_f:
out:
show();
cout << "rc=" << rc << endl;
return rc;
}
g(x)が失敗したf(x)を取り消すためにundo_f(x)を呼び出す必要がある。
副作用をおこすブロック(この例だとf()とかg())毎に対応する
gotoラベルを用意してリカバリルーチンを逆順に呼んでいく。
問題はf(),g()の部分とリカバリの部分がコード上で離れてしまうのと
逆順に書くの人間がやらないといけないところ。f()とg()の順序が逆になったときや
f()とg()の間にh()が入ったときにリカバリ部分も書き換えないといけない。
たぶん書き換えるのは忘れないとおもうが、実装ミスをする個所が増える。
C++の例外処理をつかった例
#include <iostream>
#include <cstdlib>
using std::cout;
using std::endl;
int a = 0;
int b = 0;
void
show()
{
cout << "a=" << a << endl
<< "b=" << b << endl;
}
struct ERR {};
void
f(int x)
{
if (x > 100)
throw ERR();
a += x;
}
void
undo_f(int x)
{
a -= x;
}
void
g(int x)
{
if (x > 10)
throw ERR();
b += x;
}
int
main(int ac, char **av)
{
int rc = 1;
int x = av[1] ? atoi(av[1]) : 0;
try {
f(x);
try {
g(x);
rc = 0;
}
catch (ERR&) {
undo_f(x);
}
}
catch (ERR&) {
}
show();
cout << "rc=" << rc << endl;
return rc;
}
tryブロックの入れ子で実装している。gotoパターンに比べて、正常系の処理とエラーリカバリの処理とが
構文上のブロックとして関連づけられるのでミスが減るが、外のtryブロックほどcatchブロックが離れてしまうので
コード上の見通しがわるくなる。
おなじく例外処理をつかう別の例
#include <iostream>
#include <stdexcept>
#include <cstdlib>
using std::cout;
using std::endl;
using std::exception;
using std::runtime_error;
int a = 0;
int b = 0;
void
show()
{
cout << "a=" << a << endl
<< "b=" << b << endl;
}
struct ERR {};
void
f(int x)
{
if (x > 100)
throw ERR();
a += x;
}
void
undo_f(int x)
{
a -= x;
}
void
g(int x)
{
if (x > 10)
throw ERR();
b += x;
}
void
proc2(int x)
{
try {
g(x);
}
catch (ERR&) {
throw;
}
}
void
proc1(int x)
{
f(x);
try {
proc2(x);
}
catch (ERR&) {
undo_f(x);
throw;
}
}
int
main(int ac, char **av)
{
int rc = 1;
int x = av[1] ? atoi(av[1]) : 0;
try {
proc1(x);
rc = 0;
}
catch (ERR&) {
}
show();
cout << "rc=" << rc << endl;
return rc;
}
1関数1処理に限定するように書くことで構文上のtryの入れ子を避けている。
呼び出し関係から実際にはtryは入れ子になっている。
C++0xで入るlambdaをつかった例
#include <iostream>
#include <stdexcept>
#include <cstdlib>
using std::cout;
using std::endl;
using std::exception;
using std::runtime_error;
int a = 0;
int b = 0;
void
show()
{
cout << "a=" << a << endl
<< "b=" << b << endl;
}
struct ERR {};
void
f(int x)
{
if (x > 100)
throw ERR();
a += x;
}
void
undo_f(int x)
{
a -= x;
}
void
g(int x)
{
if (x > 10)
throw ERR();
b += x;
}
template <typename F>
struct cmd
{
F* pf;
cmd(F& f) : pf(&f) {}
~cmd() { if (pf) (*pf)(); }
void clear() { pf = 0; }
};
template <typename F>
cmd<F>
make_cmd(F f)
{
return cmd<F>(f);
}
int
main(int ac, char **av)
{
int rc = 1;
int x = av[1] ? atoi(av[1]) : 0;
try {
f(x);
auto uf (make_cmd([&x]() { undo_f(x); }));
g(x);
uf.clear(); //gが成功したらfのundoは不要。
rc = 0;
}
catch (ERR&) {
}
show();
cout << "rc=" << rc << endl;
return rc;
}














