私はどこかでルールを読みました:
1回のエントリー/1回の出口ルールに従います。同じ関数内に複数の return ステートメントを記述しないでください。
この声明は本当ですか?もしそうなら、なぜ私たちがこの規則に従うべきなのか、もっと詳しく教えていただけますか?
私はどこかでルールを読みました:
1回のエントリー/1回の出口ルールに従います。同じ関数内に複数の return ステートメントを記述しないでください。
この声明は本当ですか?もしそうなら、なぜ私たちがこの規則に従うべきなのか、もっと詳しく教えていただけますか?
これは本当ですか?
それが、ルールが使用され、実施されている場所で、ルールが言っていることです。それは良いルールですか?私はその採用歯と爪と戦う。馬鹿げたルールだと思います。ばかばかしいよりも悪い: これは C++ にとって有害なルールです。
ルールの最初の部分である「シングルエントリー」に同意します。Fortranentry
ステートメントは、解決するよりも多くの問題を引き起こします。このルールの最初の部分は、C または C++ には関係ありません。単純な理由は、どちらの言語も複数のエントリ ポイント メカニズムを提供しないからです。「単一エントリ」は、C および C++ ではノーオペレーションです。
では、「片道」はどうでしょうか。早期復帰が必ずしも問題を引き起こすわけではありません。戻る前に割り当てられたリソースを処理しないと、問題が発生します。正しいルールは、「混乱を一掃する」、つまりぶら下がっているリソースを放置しないことです。単一の出口では、混乱を一掃することについて何も言わないため、この問題は解決しません。
goto
C では、通常、1 つのエントリ/1 つの終了ルールは、 for エラー処理の使用を許可する (さらには奨励する) ことと密接に関連しています。goto
Linux カーネル コードでエラー処理に使用されている場所がわかります。しかし、C++ ではありません。これが、C++ では単一のエントリ/単一の終了が有害であると書いた理由です。このルールは、RAII と例外セーフ プログラミングの使用を推奨せず、goto
.
いいえ、これはルールではありません。達成が困難または不可能な場合もあります。ただし、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 にブレークポイントを配置して、これが関数が終了するポイントであり、確実にヒットすることを知ることもできます。
この規則は 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?
また、複数の出口がパフォーマンスの問題になる可能性があります。プロセッサが現在のコマンドを実行すると、同じクロックティックでいくつかの次のコマンドが処理され、それらを使用していくつかの操作が実行されます。したがって、コードに次のように複数の出口がある場合:
if (condition)
return a;
DoSomething();
if (condition2)
return b;
最初の条件が真の場合、DoSomething()
コマンドの抽出は役に立ちません。実際、分岐予測を使用しても問題はありませんが、とにかくこのことを念頭に置いておくとよいでしょう。
これを実現したい場合は、戻り値を変数に格納できますが、そうではありません。問題なく、同じ関数で複数の戻り値を持つことができます。
私は個人的に早期終了に反対していませんが、検討のためにSingerOfTheFallの3番目の代替案を提案します。
利点:
不利益:
int foo() {{ int errorCode = 0; もし何か) { //100行のコード もし何か) { //100行のコード もし何か) { //100行のコード } そうしないと { errorCode = -1; } } そうしないと { errorCode = 11; } } そうしないと { errorCode = 1; } errorCodeを返します。 }