Ich habe zahlreiche Argumente gesehen, dass die Verwendung eines Rückgabewerts Out-Parametern vorzuziehen ist. Ich bin überzeugt von den Gründen, warum man sie vermeiden sollte, aber ich bin unsicher, ob ich auf Fälle stoße, in denen es unvermeidbar ist.
Teil eins meiner Frage ist: Was sind einige Ihrer bevorzugten / gebräuchlichen Wege, um mit einem out-Parameter herumzukommen? Sachen in der Art: Mann, in Peer Reviews sehe ich immer andere Programmierer das machen, wenn sie es so leicht hätten machen können.
Teil Zwei meiner Frage behandelt einige spezifische Fälle, in denen ich einen out-Parameter vermeiden wollte, aber mir keinen sauberen Weg vorstellen kann.
Beispiel 1: Ich habe eine Klasse mit einer teuren Kopie, die ich gerne vermeiden würde. Am Objekt kann gearbeitet werden, und das baut das Objekt auf, das teuer zu kopieren ist. Die Arbeit zum Aufbau der Daten ist auch nicht gerade trivial. Derzeit werde ich dieses Objekt in eine Funktion übergeben, die den Zustand des Objekts ändern wird. Dies ist für mich vorzuziehen, das Objekt, das der Worker-Funktion intern ist, neu zu erstellen und es zurückzugeben, da es mir erlaubt, Dinge auf dem Stapel zu halten.
%Vor%Wenn ich meine Funktion benutze, rufe ich Code wie folgt auf:
%Vor%Ich hätte gerne so etwas:
%Vor%Aber ich weiß nicht, ob das möglich ist.
Zweites Beispiel: Ich habe eine Reihe von Objekten. Die Objekte im Array sind ein komplexer Typ, und ich muss an jedem Element arbeiten, das ich von der Hauptschleife, die auf jedes Element zugreift, getrennt halten möchte. Der Code sieht momentan so aus:
%Vor%Irgendwelche Vorschläge, wie Sie mit einigen dieser Situationen umgehen können? Ich arbeite hauptsächlich in C ++, aber ich bin gespannt, ob andere Sprachen die Einrichtung erleichtern. Ich habe RVO als eine mögliche Lösung angetroffen, aber ich muss mehr darüber lesen und es klingt wie ein Compiler-spezifische Funktion.
Ich bin mir nicht sicher, warum Sie versuchen, hier Referenzen zu vermeiden. Es ist ziemlich genau diese Situationen, die eine Semantik für die Weitergabe durch Referenz existieren.
Der Code
%Vor%sieht für mich vollkommen in Ordnung aus.
Wenn Sie es wirklich ändern wollen, dann haben Sie ein paar Optionen
Jeder nützliche Compiler führt RVO (Rückgabewert-Optimierung) aus, wenn Optimierungen aktiviert sind, daher führt das Folgende effektiv nicht zum Kopieren:
%Vor%In einigen Fällen können Compiler NRVO, auch Rückgabewertoptimierung genannt, anwenden:
%Vor%Dies ist jedoch nicht genau zuverlässig, funktioniert nur in trivialeren Fällen und müsste getestet werden. Wenn Sie nicht in der Lage sind, jeden Fall zu testen, verwenden Sie einfach out-Parameter mit Referenzen im zweiten Fall.
IMO das erste, was Sie sich fragen sollten ist, ob das Kopieren von ExpensiveCopy
wirklich so teuer ist. Um das zu beantworten, benötigen Sie normalerweise einen Profiler. Wenn Ihnen ein Profiler nicht sagt, dass das Kopieren wirklich ein Flaschenhals ist, schreiben Sie einfach den Code, der leichter zu lesen ist: ExpensiveCopy obj = doWork(param);
.
Natürlich gibt es Fälle, in denen Objekte aus Performance- oder anderen Gründen nicht kopiert werden können. Dann gilt Neils Antwort .
Zusätzlich zu allen Kommentaren hier würde ich erwähnen, dass in C ++ 0x Sie selten Ausgabeparameter für Optimierungszwecke verwenden würden - wegen Move Constructors (siehe hier )
Es sei denn, Sie gehen die Route "alles ist unveränderlich" durch, was nicht gut mit C ++ übereinstimmt. Sie können Parameter nicht leicht vermeiden. Die C ++ - Standardbibliothek verwendet sie, und was gut genug für sie ist, ist gut genug für mich.
Was Ihr erstes Beispiel betrifft: Rückgabewert-Optimierung ermöglicht es oft, das zurückgegebene Objekt direkt an Ort und Stelle zu erstellen, anstatt das Objekt herum kopieren zu müssen. Alle modernen Compiler machen das.
An welcher Plattform arbeiten Sie?
Der Grund, warum ich frage, ist, dass viele Leute die Rückgabewertoptimierung vorgeschlagen haben, die eine sehr praktische Compiler-Optimierung ist, die in fast jedem Compiler vorhanden ist. Darüber hinaus implementieren Microsoft und Intel das, was sie benannte Rückgabewert-Optimierung nennen, was noch praktischer ist.
In der Standard-Rückgabewert-Optimierung ist Ihre return-Anweisung ein Aufruf an den Konstruktor eines Objekts, der den Compiler anweist, die temporären Werte zu eliminieren (nicht unbedingt die Kopieroperation).
In der benannten Rückgabewert-Optimierung können Sie einen Wert nach seinem Namen zurückgeben und der Compiler wird dasselbe tun. Der Vorteil von NRVO ist, dass Sie komplexere Operationen mit dem erstellten Wert ausführen können (wie das Aufrufen von Funktionen), bevor Sie ihn zurückgeben.
Obwohl keine von beiden eine teure Kopie wirklich eliminiert, wenn Ihre zurückgegebenen Daten sehr groß sind, helfen sie.
Was die Vermeidung der Kopie betrifft, so ist dies nur mit Zeigern oder Referenzen möglich, da Ihre Funktion die Daten an der Stelle ändern muss, an der sie enden soll. Das bedeutet, dass Sie wahrscheinlich einen Pass haben möchten -By-Referenzparameter.
Ich möchte auch darauf hinweisen, dass Pass-by-Reference im Hochleistungscode aus diesem Grund sehr gebräuchlich ist. Kopieren von Daten kann unglaublich teuer sein, und oft übersehen die Leute, wenn sie ihren Code optimieren.
Soweit ich sehen kann, sind die Gründe, die Rückgabewerte den out-Parametern vorzuziehen, klarer und funktionieren mit reiner funktionaler Programmierung (Sie können einige nette Garantien bekommen, wenn eine Funktion nur von Eingabeparametern abhängt, gibt einen Wert zurück und hat keine Nebenwirkungen). Der erste Grund ist stilistisch und meiner Meinung nach nicht so wichtig. Die zweite passt nicht gut zu C ++. Daher würde ich nicht versuchen, irgendetwas zu verzerren, um Parameter zu vermeiden.
Die einfache Tatsache ist, dass einige Funktionen mehrere Dinge zurückgeben müssen, und in den meisten Sprachen deutet dies auf Parameter hin. Common Lisp hat multiple-value-bind
und multiple-value-return
, in denen eine Liste von Symbolen durch die Bindung bereitgestellt wird und eine Liste von Werten zurückgegeben wird. In einigen Fällen kann eine Funktion einen zusammengesetzten Wert zurückgeben, z. B. eine Liste von Werten, die dann dekonstruiert werden, und es ist keine große Sache, dass eine C ++ - Funktion ein std::pair
zurückgibt. Die Rückgabe von mehr als zwei Werten auf diese Weise in C ++ wird umständlich. Es ist immer möglich, eine Struktur zu definieren, aber das Definieren und Erstellen ist oft unordentlicher als die Out-Parameter.
In einigen Fällen wird der Rückgabewert überlastet. In C gibt getchar()
ein int zurück, mit der Idee, dass es mehr int-Werte als char gibt (wahr in allen Implementierungen, die ich kenne, falsch in einigen, die ich mir leicht vorstellen kann), daher kann einer der Werte verwendet werden Ende der Datei. atoi()
gibt eine ganze Zahl zurück, entweder die Ganzzahl, die durch die übergebene Zeichenkette repräsentiert wird, oder Null, falls keine vorhanden ist, also gibt es dasselbe für "0" und "Frosch" zurück. (Wenn Sie wissen möchten, ob ein int-Wert vorhanden war oder nicht, verwenden Sie strtol()
, das einen out-Parameter hat.)
Es gibt immer die Technik, im Falle eines Fehlers eine Exception auszulösen, aber nicht alle multiplen Rückgabewerte sind Fehler, und nicht alle Fehler sind außergewöhnlich.
Überladene Rückgabewerte verursachen also Probleme, Mehrfachwert-Rückgabewerte sind nicht einfach in allen Sprachen zu verwenden, und Einzelrückläufer sind nicht immer vorhanden. Eine Ausnahme zu werfen ist oft unangemessen. Die Verwendung von out-Parametern ist sehr oft die sauberste Lösung.
Fragen Sie sich, warum Sie eine Methode haben, mit der Sie dieses teure Objekt erst kopieren können. Sagen wir, du hast einen Baum, würdest du den Baum in eine Baumethode schicken oder dem Baum eine eigene Baumethode geben? Situationen wie diese kommen ständig auf, wenn Sie ein wenig vom Design abweichen, aber dazu neigen, sich in sich selbst zu falten, wenn Sie es unten haben.
Ich weiß, dass wir in der Praxis nicht immer jedes Objekt ändern können, aber das Eingeben von Parametern ist eine Nebeneffekt-Operation, und es macht es viel schwieriger herauszufinden, was vor sich geht, und das muss man nie wirklich tue es (außer durch die Arbeit in anderen Code-Frameworks gezwungen).
Manchmal ist es einfacher, aber es ist definitiv nicht wünschenswert, es ohne Grund zu benutzen (wenn du ein paar große Projekte durchgemacht hast, bei denen immer ein halbes Dutzend Parameter fehlen, wirst du wissen, was ich meine).
>Tags und Links c++ function oop parameters out-parameters