9

私はどこかでルールを読みました:

1回のエントリー/1回の出口ルールに従います。同じ関数内に複数の return ステートメントを記述しないでください。

この声明は本当ですか?もしそうなら、なぜ私たちがこの規則に従うべきなのか、もっと詳しく教えていただけますか?

4

6 に答える 6

24

これは本当ですか?

それが、ルールが使用され、実施されている場所で、ルールが言っていることです。それは良いルールですか?私はその採用歯と爪と戦う。馬鹿げたルールだと思います。ばかばかしいよりも悪い: これは C++ にとって有害なルールです。

ルールの最初の部分である「シングルエントリー」に同意します。Fortranentryステートメントは、解決するよりも多くの問題を引き起こします。このルールの最初の部分は、C または C++ には関係ありません。単純な理由は、どちらの言語も複数のエントリ ポイント メカニズムを提供しないからです。「単一エントリ」は、C および C++ ではノーオペレーションです。

では、「片道」はどうでしょうか。早期復帰が必ずしも問題を引き起こすわけではありません。戻る前に割り当てられたリソースを処理しないと、問題が発生します。正しいルールは、「混乱を一掃する」、つまりぶら下がっているリソースを放置しないことです。単一の出口では、混乱を一掃することについて何も言わないため、この問題は解決しません。

gotoC では、通常、1 つのエントリ/1 つの終了ルールは、 for エラー処理の使用を許可する (さらには奨励する) ことと密接に関連しています。gotoLinux カーネル コードでエラー処理に使用されている場所がわかります。しかし、C++ ではありません。これが、C++ では単一のエントリ/単一の終了が有害であると書いた理由です。このルールは、RAII と例外セーフ プログラミングの使用を推奨せず、goto.

于 2012-10-05T12:20:53.017 に答える
12

いいえ、これはルールではありません。達成が困難または不可能な場合もあります。ただし、1 つのエントリ ポイントと 1 つの終了ポイントを持つコードは、理解とデバッグが容易です。これを比較してください:

int foo()
{
    if(something)
        return 0;
    //100 lines of code
    if(something)
        return 11;
    //100 lines of code
    if(something)
        return -1;
    //100 lines of code
    return 0;
}

この:

int foo()
{
    int errorCode = 0;
    if(something)
        errorCode = 1;
    //100 lines of code
    if(something)
        errorCode = 11;
    //100 lines of code
    if(something)
        errorCode = -1;
    //100 lines of code
    return errorCode;
}

これで、出口点が 1 つだけになり、(変数名も考慮して) 関数が何をするかを理解するのがはるかに簡単になりました。また、最後の return にブレークポイントを配置して、これが関数が終了するポイントであり、確実にヒットすることを知ることもできます。

于 2012-10-05T11:39:13.480 に答える
6

この規則は C に適用される可能性がありますが、例外があるため、C++ では廃止されたと見なすことができます。関数が例外をスローするか、スローできる関数を呼び出すとすぐに、追加の終了ポイントがあります。

int f()
{
  //...
  g(); // g() may throw: you have an exit point here
  //...
  throw exc; // another possible exit point
  //...
  return returnValue; // Nice try, but you have additional exit points
}

これは、他の回答で行われたポイントに加えて、コードを理解しやすくすることを目的としていますが、これが当てはまらない例を簡単に見つけることができます。ずっといい:

if (condition)
  return a;
if (condition2)
  return b;
if (condition3)
  return c;

// Insert all your code for the general case

よりも:

int returnValue;    
if (!condition) {
  if (!condition2) {
    if (!condition3) {
      // Insert your code here
    }
    else {
      returnValue = c;
    }
    returnValue = b;  // Where am I now?
  }
  returnValue = a;
}
return returnValue;

そして、 a で戻り値を決定する場合もありますswitch

switch (a)
{
  case 1: return 10;
  case 2: return 20;
  case 3: return 40;
  default: return 50;
}

それよりも:

int returnValue;
switch (a)
{
  case 1: returnValue = 10; break;
  case 2: returnValue = 20; break;
  case 3: returnValue = 40; break;
  default: returnValue = 50; break;
}
return returnValue; // Where is the clarity gained?
于 2012-10-05T11:54:18.047 に答える
1

また、複数の出口がパフォーマンスの問題になる可能性があります。プロセッサが現在のコマンドを実行すると、同じクロックティックでいくつかの次のコマンドが処理され、それらを使用していくつかの操作が実行されます。したがって、コードに次のように複数の出口がある場合:

if (condition)
  return a;
DoSomething();
if (condition2)
  return b;

最初の条件が真の場合、DoSomething()コマンドの抽出は役に立ちません。実際、分岐予測を使用しても問題はありませんが、とにかくこのことを念頭に置いておくとよいでしょう。

于 2012-10-05T12:03:09.680 に答える
0

これを実現したい場合は、戻り値を変数に格納できますが、そうではありません。問題なく、同じ関数で複数の戻り値を持つことができます。

于 2012-10-05T11:41:46.570 に答える
-3

私は個人的に早期終了に反対していませんが、検討のためにSingerOfTheFallの3番目の代替案を提案します。

利点:

  • 通常のコードフロー(つまり、エラー以外)は、コードの先頭をきれいに流れます
  • ある「何か」が失敗し、別の「何か」が誤ってコードのブロックを実行する可能性はありません。
  • コードブロックにスコープを適用できます。コードのサブブロックで使用されるもののスコープの終了時のクリーンアップを含む

不利益:

  • インデントは合計される可能性があります(ただし、これはサブ機能に分割することで軽減できます)
  • エディターで中括弧が一致しないと、エラーと失敗した状態を一致させることが困難になる可能性があります
int foo()
{{
    int errorCode = 0;
    もし何か) {
        //100行のコード
        もし何か) {
            //100行のコード
            もし何か) {
                //100行のコード
            }
            そうしないと {
                errorCode = -1;
            }
        }
        そうしないと {
            errorCode = 11;
       }
    }
    そうしないと {
        errorCode = 1;
    }
    errorCodeを返します。
}
于 2012-10-05T12:04:04.180 に答える