私もこのパターンを使用しています。不思議に思う人のために、ここに抽象的な例を示します。
do // while(0) for break
{
state1 = 0;
if (cond1())
{
if (cond2())
break;
state1 = opA();
}
if (cond3() || state1 && state1->cond4())
break;
...
Triumph(state1, ...);
// often here: return
}
Failure(state1, ...);
これは、次の状況で有効だと思います。
- あなたは長いシーケンスを持っています(たとえば、>〜半ダースの条件)
- 条件が複雑で、重要な状態を使用/構築するため、要素を関数に分離することはできません
- 例外に適していない環境にいるか、
break
-ing コード パスが実際には例外ではありません
それについてあなたができること:
警告を黙らせます。結局のところ、これは単なる警告です。この警告によってキャッチされる「典型的な間違い」(条件の代わりに 0 を入力するなど) は見当たりません。
[編集] さて、それはばかげていました。警告でキャッチする典型的な間違いは、 .[/edit]while (a1!=a1)
ではなく、たとえばです。while (a1!=a2)
関数に分割し、状態をクラスに移動します。
これにより、上記のコードが次のように変換されます。
struct Garbler
{
State1 state1;
bool Step1()
{
state1 = 0;
if (cond1())
{
if (cond2())
return false;
state1 = opA();
}
return true;
}
bool Step2()
{
return cond3() || state1 && state1->cond4();
}
..
void Run()
{
if (Step1() && Step2() && ... && Step23())
Triumph(state1, ...);
else
Failure(state1, ...);
}
}
これは間違いなく読みにくく、さらに悪いことに、シーケンスを分解すると、非常に疑わしいクラス (メンバーが特定の順序でのみ呼び出される可能性がある) につながる可能性があります。
Scopeguards
これにより、ブレークを早期のリターンに変換できる場合があり、より受け入れられます。
state1 = 0;
ScopeGuard gFailure = MakeGuard(&Failure, ByRef(state1), ...);
if (cond1())
{
if (cond2())
return;
state1 = opA();
}
if (cond3() || state1 && state1->cond4())
return;
// everything went ok, we can dismiss the scopeguard
gFailure.Dismiss();
Triumph(state1, ...);
それらは C++0x でよりエレガントに記述でき、フローを維持できますが、ソリューションはそれほど柔軟ではありません。たとえばFailure()
、単一の関数に簡単に分離できない場合などです。