Ich würde einen einzigen Ausgang bevorzugen, es sei denn, es macht die Sache wirklich kompliziert. Ich habe die Erfahrung gemacht, dass mehrere Ausgänge in manchen Fällen andere, wichtigere Designprobleme überdecken 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?
Je nach den Antworten auf diese Fragen könnte es sein, dass
- die Prüfung ist sinnlos, da sie nie wahr ist (d.h. sie sollte eine Behauptung sein)
- die Prüfung ist sehr selten wahr und es ist daher vielleicht besser, diese speziellen Aufruferfunktionen zu ändern, da sie wahrscheinlich ohnehin eine andere Aktion durchführen sollten.
In beiden oben genannten Fällen kann der Code wahrscheinlich mit einer Assertion überarbeitet werden, um sicherzustellen, dass "foo" niemals null ist, und die entsprechenden Aufrufer geändert werden.
Es gibt zwei weitere Gründe (spezifisch für C++-Code, denke ich), bei denen mehrere Existenzen tatsächlich eine negativ beeinflussen. Das sind die Codegröße und die Compiler-Optimierungen.
Bei einem Nicht-POD-C++-Objekt, das sich beim Verlassen einer Funktion im Anwendungsbereich befindet, wird sein Destruktor aufgerufen. Bei mehreren Rückgabeanweisungen kann es vorkommen, dass sich verschiedene Objekte im Anwendungsbereich befinden und somit die Liste der aufzurufenden Destruktoren unterschiedlich ist. Der Compiler muss daher für jede Rückgabeanweisung Code erzeugen:
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 Größe des Codes eine Rolle spielt, sollte man dies vielleicht vermeiden.
Das andere Problem bezieht sich auf die "Named Return Value OptimiZation" (auch bekannt als Copy Elision, ISO C++ '03 12.8/15). C++ erlaubt es einer Implementierung, den Aufruf des Kopierkonstruktors zu überspringen, wenn sie es kann:
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. Die Copy Elision erlaubt es dem Compiler jedoch, 'a1' an der gleichen Stelle auf dem Stack zu konstruieren wie 'a2'. Es besteht daher keine Notwendigkeit, das Objekt zu "kopieren", wenn die Funktion zurückkehrt.
Mehrere Exit-Points erschweren die Arbeit des Compilers bei dem Versuch, dies zu erkennen, und zumindest bei einer relativ neuen Version von VC++ fand die Optimierung nicht statt, wenn der Funktionskörper mehrere Rückgaben hatte. Siehe Optimierung von benannten Rückgabewerten in Visual C++ 2005 für weitere Einzelheiten.
25 Stimmen
Ich stimme nicht zu, dass die Frage sprachenfeindlich ist. Bei einigen Sprachen ist die Mehrfachrückgabe natürlicher und bequemer als bei anderen. Ich würde mich eher über frühe Rückgaben in einer C-Funktion beschweren als in einer C++-Funktion, die RAII verwendet.
3 Stimmen
Diese Frage ist eng damit verbunden und enthält ausgezeichnete Antworten: programmers.stackexchange.com/questions/118703/
0 Stimmen
Sprachunabhängig? Erklären Sie jemandem, der eine funktionale Sprache verwendet, dass er einen Return pro Funktion verwenden muss :p