興味深い答え:私はそれらのすべてに(今のところ)同意しますが、今のところ完全に無視されている可能性のあるこの質問への示唆があります。
上記の簡単な例をリソース割り当てで拡張し、エラーチェックを行ってリソースが解放される可能性がある場合は、状況が変わる可能性があります。
初心者が取るかもしれない素朴なアプローチを検討してください:
int func(..some parameters...) {
res_a a = allocate_resource_a();
if (!a) {
return 1;
}
res_b b = allocate_resource_b();
if (!b) {
free_resource_a(a);
return 2;
}
res_c c = allocate_resource_c();
if (!c) {
free_resource_b(b);
free_resource_a(a);
return 3;
}
do_work();
free_resource_c(c);
free_resource_b(b);
free_resource_a(a);
return 0;
}
上記は時期尚早に戻るスタイルの極端なバージョンを表しています。コードの複雑さが増すと、コードが時間の経過とともに非常に繰り返しやすくなり、保守できなくなることに注意してください。今日、人々はこれらをキャッチするために例外処理を使用するかもしれません。
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
try {
a = allocate_resource_a(); # throws ExceptionResA
b = allocate_resource_b(); # throws ExceptionResB
c = allocate_resource_c(); # throws ExceptionResC
do_work();
}
catch (ExceptionBase e) {
# Could use type of e here to distinguish and
# use different catch phrases here
# class ExceptionBase must be base class of ExceptionResA/B/C
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
throw e
}
return 0;
}
フィリップは、以下のgotoの例を調べた後、上のcatchブロック内でブレークなしのスイッチ/ケースを使用することを提案しました。switch(typeof(e))をfree_resourcex()
呼び出してから、呼び出しに失敗する可能性がありますが、これは簡単なことではなく、設計上の考慮が必要です。そして、切れ目のないスイッチ/ケースは、以下のデイジーチェーンされたラベルのあるgotoとまったく同じです...
Mark Bが指摘したように、C ++では、リソースの取得は初期化の原則であるRAIIに従うのが適切なスタイルと見なされます。つまりに。コンセプトの要点は、オブジェクトのインスタンス化を使用してリソースを取得することです。その後、オブジェクトがスコープ外になり、そのデストラクタが呼び出されるとすぐに、リソースは自動的に解放されます。相互に依存するリソースについては、割り当て解除の正しい順序を保証し、必要なデータがすべてのデストラクタで利用できるようにオブジェクトのタイプを設計するために、特別な注意を払う必要があります。
または、例外前の日に行う可能性があります:
int func(..some parameters...) {
res_a a = allocate_resource_a();
res_b b = allocate_resource_b();
res_c c = allocate_resource_c();
if (a && b && c) {
do_work();
}
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
return 0;
}
ただし、この過度に単純化された例にはいくつかの欠点があります。割り当てられたリソースが相互に依存しない場合にのみ使用できます(たとえば、メモリの割り当てに使用できず、ファイルハンドルを開いて、ハンドルからメモリにデータを読み取ります) )、戻り値として個別の識別可能なエラーコードを提供しません。
コードを高速(!)、コンパクト、簡単に読み取り、拡張できるようにするために、Linus Torvaldsは、悪名高いgotoを絶対的に意味のある方法で使用していても、リソースを処理するカーネルコードに異なるスタイルを適用しました。
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
a = allocate_resource_a() || goto error_a;
b = allocate_resource_b() || goto error_b;
c = allocate_resource_c() || goto error_c;
do_work();
error_c:
free_resource_c(c);
error_b:
free_resource_b(b);
error_a:
free_resource_a(a);
return 0;
}
カーネルメーリングリストに関する議論の要点は、gotoステートメントよりも「推奨される」ほとんどの言語機能が、巨大なツリーのようなif / else、例外ハンドラー、ループ/ブレーク/継続ステートメントなどの暗黙のゴトであることです。 。上記の例のgotoは、ジャンプする距離が短く、明確なラベルがあり、エラー状態を追跡するために他の混乱のコードを解放するため、問題ありません。この質問については、stackoverflowでも説明しています。。
ただし、前の例で欠けているのは、エラーコードを返す良い方法です。result_code++
それぞれにアフターを追加することを考えていましたfree_resource_x()
呼び出しのそのコードを返すこといましたが、これは上記のコーディングスタイルの速度向上の一部を相殺します。また、成功した場合に0を返すのは困難です。たぶん私は想像を絶するだけです;-)
だから、はい、時期尚早のリターンのコーディングの問題には大きな違いがあると思います。しかし、私はまた、コンパイラー用に再構成して最適化することがより困難または不可能である、より複雑なコードでのみ明らかになると思います。これは通常、リソース割り当てが機能するようになった場合に当てはまります。