Ich würde einen einzelnen Ausgang bevorzugen, es sei denn, dies erschwert die Dinge wirklich. Ich habe festgestellt, dass in einigen Fällen mehrere vorhandene Punkte andere wichtigere Entwurfsprobleme maskieren können:
public void DoStuff(Foo foo)
{
if (foo == null) return;
}
Wenn ich diesen Code sehe, würde ich sofort fragen:
- Ist 'foo' jemals null?
- Wenn ja, wie viele Clients von 'DoStuff' rufen die Funktion jemals mit einem Null-Foo auf?
Abhängig von den Antworten auf diese Fragen kann es sein, dass
- Die Prüfung ist sinnlos, da sie niemals wahr ist (dh es sollte eine Behauptung sein).
- Die Prüfung ist sehr selten wahr und daher ist es möglicherweise besser, diese spezifischen Anruferfunktionen zu ändern, da sie wahrscheinlich sowieso andere Maßnahmen ergreifen sollten.
In beiden oben genannten Fällen kann der Code wahrscheinlich mit einer Zusicherung überarbeitet werden, um sicherzustellen, dass 'foo' niemals null ist und die relevanten Anrufer geändert werden.
Es gibt zwei weitere Gründe (speziell für C ++ - Code), bei denen mehrere tatsächlich negative Auswirkungen haben können. Sie sind Codegröße und Compiler-Optimierungen.
Bei einem Nicht-POD-C ++ - Objekt im Gültigkeitsbereich am Ende einer Funktion wird der Destruktor aufgerufen. Bei mehreren return-Anweisungen kann es vorkommen, dass sich unterschiedliche Objekte im Gültigkeitsbereich befinden und die Liste der aufzurufenden Destruktoren daher unterschiedlich ist. Der Compiler muss daher für jede return-Anweisung Code generieren:
void foo (int i, int j) {
A a;
if (i > 0) {
B b;
return ; // Call dtor for 'b' followed by 'a'
}
if (i == j) {
C c;
B b;
return ; // Call dtor for 'b', 'c' and then 'a'
}
return 'a' // Call dtor for 'a'
}
Wenn die Codegröße ein Problem darstellt, sollte dies vermieden werden.
Das andere Problem betrifft "Named Return Value OptimiZation" (auch bekannt als Copy Elision, ISO C ++ '03 12.8 / 15). Mit C ++ kann eine Implementierung den Aufruf des Kopierkonstruktors überspringen, wenn dies möglich ist:
A foo () {
A a1;
// do something
return a1;
}
void bar () {
A a2 ( foo() );
}
Wenn man den Code so nimmt, wie er ist, wird das Objekt 'a1' in 'foo' konstruiert und dann wird sein Kopierkonstrukt aufgerufen, um 'a2' zu konstruieren. Mit Copy Elision kann der Compiler jedoch 'a1' an derselben Stelle auf dem Stapel wie 'a2' erstellen. Es ist daher nicht erforderlich, das Objekt zu "kopieren", wenn die Funktion zurückkehrt.
Mehrere Exit-Punkte erschweren die Arbeit des Compilers, dies zu erkennen, und zumindest für eine relativ neue Version von VC ++ fand die Optimierung nicht statt, wenn der Funktionskörper mehrere Rückgaben hatte. Siehe Named Rückgabewert Optimierung in Visual C ++ 2005 für weitere Details.