Ist es in C ++ besser, einen Wert oder eine konstante Referenz zu übergeben?
Ich frage mich, welche Praxis besser ist. Mir ist klar, dass das Übergeben einer konstanten Referenz für eine bessere Leistung im Programm sorgen sollte, da Sie keine Kopie der Variablen erstellen.
c++
variables
pass-by-reference
constants
pass-by-value
Matt Pascoe
quelle
quelle
Antworten:
Es verwendet , um beste Praxis in der Regel empfohlen werden 1 zu verwenden Pass von const ref für alle Typen , mit Ausnahme von eingebauten Typen (
char
,int
,double
, etc.), für Iteratoren und Funktionsobjekte (Lambda - Ausdrücke, Klassen die sich ausstd::*_function
).Dies galt insbesondere vor der Existenz der Bewegungssemantik . Der Grund ist einfach: Wenn Sie einen Wert übergeben haben, musste eine Kopie des Objekts erstellt werden. Mit Ausnahme sehr kleiner Objekte ist dies immer teurer als das Übergeben einer Referenz.
Mit C ++ 11 haben wir die Bewegungssemantik gewonnen . Kurz gesagt, die Bewegungssemantik ermöglicht es, dass ein Objekt in einigen Fällen "nach Wert" übergeben werden kann, ohne es zu kopieren. Dies ist insbesondere dann der Fall, wenn das Objekt, das Sie übergeben, ein r-Wert ist .
An sich ist das Bewegen eines Objekts immer noch mindestens so teuer wie das Übergeben als Referenz. In vielen Fällen kopiert eine Funktion ein Objekt jedoch trotzdem intern - dh sie übernimmt das Eigentum an dem Argument. 2
In diesen Situationen haben wir den folgenden (vereinfachten) Kompromiss:
"Wert übergeben" bewirkt weiterhin, dass das Objekt kopiert wird, es sei denn, das Objekt ist ein r-Wert. Im Falle eines r-Werts kann das Objekt stattdessen verschoben werden, so dass der zweite Fall plötzlich nicht mehr "kopieren, dann verschieben", sondern "verschieben, dann (möglicherweise) wieder verschieben" lautet.
Bei großen Objekten, die geeignete Verschiebungskonstruktoren implementieren (wie Vektoren, Zeichenfolgen usw.), ist der zweite Fall dann weitaus effizienter als der erste. Daher wird empfohlen, den Wert " Übergeben" zu verwenden, wenn die Funktion den Besitz des Arguments übernimmt und der Objekttyp ein effizientes Verschieben unterstützt .
Eine historische Anmerkung:
Tatsächlich sollte jeder moderne Compiler in der Lage sein, herauszufinden, wann das Übergeben von Werten teuer ist, und den Aufruf implizit so zu konvertieren, dass er nach Möglichkeit eine const ref verwendet.
In der Theorie. In der Praxis können Compiler dies nicht immer ändern, ohne die binäre Schnittstelle der Funktion zu beschädigen. In einigen speziellen Fällen (wenn die Funktion inline ist) wird die Kopie tatsächlich entfernt, wenn der Compiler herausfinden kann, dass das ursprüngliche Objekt durch die Aktionen in der Funktion nicht geändert wird.
Im Allgemeinen kann der Compiler dies jedoch nicht feststellen, und das Aufkommen der Verschiebungssemantik in C ++ hat diese Optimierung viel weniger relevant gemacht.
1 ZB in Scott Meyers, Effektives C ++ .
2 Dies gilt insbesondere häufig für Objektkonstruktoren, die Argumente verwenden und intern speichern, um Teil des Status des konstruierten Objekts zu sein.
quelle
Edit: Neuer Artikel von Dave Abrahams auf cpp-next:
Willst du Geschwindigkeit? Wert übergeben.
Das Übergeben eines Werts für Strukturen, bei denen das Kopieren billig ist, hat den zusätzlichen Vorteil, dass der Compiler davon ausgehen kann, dass die Objekte keinen Alias haben (nicht dieselben Objekte sind). Bei Verwendung der Referenzübergabe kann der Compiler dies nicht immer annehmen. Einfaches Beispiel:
Der Compiler kann es optimieren
da es weiß, dass f und g nicht den gleichen Ort haben. Wenn g eine Referenz wäre (foo &), hätte der Compiler das nicht annehmen können. da gi dann durch f-> i aliasiert werden könnte und einen Wert von 7 haben müsste, müsste der Compiler den neuen Wert von gi erneut aus dem Speicher abrufen.
Weitere praktische Regeln finden Sie im Artikel zum Verschieben von Konstruktoren (empfohlene Lektüre).
"Primitiv" bedeutet im Grunde genommen kleine Datentypen, die einige Bytes lang und nicht polymorph (Iteratoren, Funktionsobjekte usw.) oder teuer zu kopieren sind. In diesem Artikel gibt es eine andere Regel. Die Idee ist, dass man manchmal eine Kopie erstellen möchte (falls das Argument nicht geändert werden kann) und manchmal nicht (falls man das Argument selbst in der Funktion verwenden möchte, wenn das Argument ohnehin temporär war , zum Beispiel). Das Papier erklärt ausführlich, wie das gemacht werden kann. In C ++ 1x kann diese Technik nativ mit Sprachunterstützung verwendet werden. Bis dahin würde ich mit den oben genannten Regeln gehen.
Beispiele: Um einen String in Großbuchstaben zu schreiben und die Großbuchstabenversion zurückzugeben, sollte immer der Wert übergeben werden: Man muss sowieso eine Kopie davon erstellen (man konnte die const-Referenz nicht direkt ändern) - also machen Sie es besser so transparent wie möglich den Anrufer und erstellen Sie diese Kopie frühzeitig, damit der Anrufer so viel wie möglich optimieren kann - wie in diesem Dokument beschrieben:
Wenn Sie den Parameter jedoch ohnehin nicht ändern müssen, beziehen Sie sich auf const:
Wenn Sie jedoch den Zweck des Parameters haben, etwas in das Argument zu schreiben, übergeben Sie es als nicht konstante Referenz
quelle
__restrict__
(die auch für Referenzen geeignet sind ), als übermäßige Kopien zu erstellen. Schade, dass Standard-C ++ dasrestrict
Schlüsselwort von C99 nicht übernommen hat .Kommt auf den Typ an. Sie fügen den kleinen Aufwand hinzu, eine Referenz und eine Dereferenzierung vornehmen zu müssen. Bei Typen mit einer Größe, die gleich oder kleiner als Zeiger ist und den Standardkopierer verwendet, ist es wahrscheinlich schneller, einen Wert zu übergeben.
quelle
Wie bereits erwähnt, hängt dies vom Typ ab. Für integrierte Datentypen ist es am besten, den Wert zu übergeben. Sogar einige sehr kleine Strukturen, wie z. B. ein Int-Paar, können durch Übergeben von Werten eine bessere Leistung erzielen.
In diesem Beispiel wird davon ausgegangen, dass Sie einen ganzzahligen Wert haben und diesen an eine andere Routine übergeben möchten. Wenn dieser Wert für die Speicherung in einem Register optimiert wurde, muss er, wenn Sie ihn als Referenz übergeben möchten, zuerst im Speicher gespeichert und dann mit einem Zeiger auf diesen Speicher auf dem Stapel abgelegt werden, um den Aufruf auszuführen. Wenn es als Wert übergeben wurde, ist nur das Register erforderlich, das auf den Stapel geschoben wird. (Die Details sind etwas komplizierter als bei verschiedenen aufrufenden Systemen und CPUs).
Wenn Sie eine Vorlagenprogrammierung durchführen, müssen Sie normalerweise immer const ref übergeben, da Sie die übergebenen Typen nicht kennen. Das Übergeben von Strafen für das Übergeben von Wertwerten ist viel schlimmer als das Übergeben eines integrierten Typs von const ref.
quelle
Dies ist, was ich normalerweise arbeite, wenn ich die Schnittstelle einer Nicht-Template-Funktion entwerfe:
Übergeben Sie den Wert, wenn die Funktion den Parameter nicht ändern möchte und der Wert billig zu kopieren ist (int, double, float, char, bool usw.). Beachten Sie, dass std :: string, std :: vector und der Rest der Container in der Standardbibliothek sind NICHT)
Übergeben Sie den const-Zeiger, wenn das Kopieren des Werts teuer ist und die Funktion den angegebenen Wert nicht ändern möchte und NULL ein Wert ist, den die Funktion verarbeitet.
Übergeben Sie einen nicht konstanten Zeiger, wenn das Kopieren des Werts teuer ist und die Funktion den angegebenen Wert ändern möchte und NULL ein Wert ist, den die Funktion verarbeitet.
Übergeben Sie die const-Referenz, wenn das Kopieren des Werts teuer ist und die Funktion den angegebenen Wert nicht ändern möchte und NULL kein gültiger Wert wäre, wenn stattdessen ein Zeiger verwendet würde.
Übergeben Sie eine nicht konstante Referenz, wenn das Kopieren des Werts teuer ist und die Funktion den angegebenen Wert ändern möchte und NULL kein gültiger Wert wäre, wenn stattdessen ein Zeiger verwendet würde.
quelle
std::optional
dem Bild hinzu und Sie benötigen keine Zeiger mehr.Klingt so, als hättest du deine Antwort bekommen. Das Übergeben von Werten ist teuer, bietet Ihnen jedoch eine Kopie, mit der Sie bei Bedarf arbeiten können.
quelle
In der Regel ist es besser, die Konstantenreferenz zu übergeben. Wenn Sie Ihr Funktionsargument jedoch lokal ändern müssen, sollten Sie die Übergabe von Werten verwenden. Für einige Grundtypen ist die Leistung im Allgemeinen gleich, sowohl für die Übergabe nach Wert als auch als Referenz. Tatsächlich wird die Referenz intern durch einen Zeiger dargestellt. Aus diesem Grund können Sie beispielsweise erwarten, dass für den Zeiger beide Übergaben in Bezug auf die Leistung gleich sind oder dass die Übergabe durch Werte aufgrund unnötiger Dereferenzierung schneller sein kann.
quelle
Als Faustregel gilt der Wert für Nicht-Klassentypen und die Konstantenreferenz für Klassen. Wenn eine Klasse wirklich klein ist, ist es wahrscheinlich besser, den Wert zu übergeben, aber der Unterschied ist minimal. Was Sie wirklich vermeiden möchten, ist, eine gigantische Klasse nach Wert zu übergeben und alles zu duplizieren - dies macht einen großen Unterschied, wenn Sie beispielsweise einen std :: -Vektor mit einigen Elementen übergeben.
quelle
std::vector
tatsächlich seine Elemente auf dem Heap zugeordnet werden und das Vektorobjekt selbst nie wächst. Oh, Moment mal. Wenn die Operation jedoch bewirkt, dass eine Kopie des Vektors erstellt wird, werden tatsächlich alle Elemente dupliziert. Das wäre schlecht.sizeof(std::vector<int>)
ist konstant, aber wenn Sie es als Wert übergeben, wird der Inhalt auch ohne Compiler-Cleverness kopiert.Wert für kleine Typen übergeben.
Übergeben Sie const-Referenzen für große Typen (die Definition von big kann zwischen den Computern variieren), ABER übergeben Sie in C ++ 11 den Wert, wenn Sie die Daten verbrauchen möchten, da Sie die Verschiebungssemantik ausnutzen können. Beispielsweise:
Jetzt würde der aufrufende Code reichen:
Und nur ein Objekt wird erstellt und direkt in das Mitglied
name_
in der Klasse verschobenPerson
. Wenn Sie an einer const-Referenz vorbeikommen, muss eine Kopie erstellt werden, in die Sie sie einfügen könnenname_
.quelle
Einfacher Unterschied: - In der Funktion haben wir Eingabe- und Ausgabeparameter. Wenn also Ihr übergebener Eingabe- und Ausgabeparameter identisch ist, verwenden Sie call by reference, andernfalls, wenn sich Eingabe- und Ausgabeparameter unterscheiden, ist es besser, call by value zu verwenden.
Beispiel
void amount(int account , int deposit , int total )
Eingabeparameter: Konto, Einzahlungsausgabeparameter: gesamt
Eingabe und Ausgabe ist unterschiedliche Verwendung Anruf von Vaule
void amount(int total , int deposit )
Input Total Deposit Output Total
quelle