Pass by value vs. pass by rvalue-Referenz
Wann sollte ich erklären, meine Funktion als:
void foo(Widget w);
im Gegensatz zu
void foo(Widget&& w);
?
Nehme an, dies ist der einzige überlastung (wie in, ich wählen Sie eine oder das andere, nicht beide und keine anderen überladungen). Keine Schablonen beteiligt. Davon ausgehen, dass die Funktion foo
müssen Sie der Besitzer der Widget
(z.B. const Widget&
ist nicht Teil dieser Diskussion). Ich bin nicht daran interessiert, jede Antwort, die außerhalb des Geltungsbereichs dieser Umstände. Siehe Nachtrag am Ende des Beitrags für warum diese Einschränkungen sind Teil der Frage.
Dem primären Unterschied, dass meine Kollegen und ich mit oben kommen kann ist, dass die rvalue-Referenz parameter zwingt Sie explizit über Kopien. Der Aufrufer ist dafür verantwortlich, eine explizite kopieren und dann Sie es mit std::move
wenn Sie möchten, eine Kopie. In der pass-by-value Fall, die Kosten für die Kopie ist versteckt:
//If foo is a pass by value function, calling + making a copy:
Widget x{};
foo(x); //Implicit copy
//Not shown: continues to use x locally
//If foo is a pass by rvalue reference function, calling + making a copy:
Widget x{};
//foo(x); //This would be a compiler error
auto copy = x; //Explicit copy
foo(std::move(copy));
//Not shown: continues to use x locally
Andere als Unterschied. Andere als Leute zu zwingen, explizit über das kopieren und verändern, wie viel syntaktischen Zucker Sie erhalten, wenn Sie die Funktion aufrufen, wie sonst sind diese unterschiedlich? Was sagen Sie, anders über die Schnittstelle? Sind Sie mehr oder weniger effizient ist als eine andere?
Andere Dinge, die meine Kollegen und ich haben schon gedacht:
- Der rvalue-Referenz parameter bedeutet, dass Sie kann bewegen das argument, aber nicht vorschreiben. Es ist möglich, dass das argument Sie übergeben beim Aufruf site wird in seinen ursprünglichen Zustand danach. Es ist auch möglich, die Funktion würde Essen/ändern das argument, ohne jemals Berufung ein move-Konstruktor aber davon ausgehen, dass, weil es war eine rvalue-Referenz, der Anrufer aufgegeben Kontrolle. Durch Wert übergeben, wenn Sie einziehen, müssen Sie davon ausgehen, dass eine Bewegung passiert; es gibt keine andere Wahl.
- Vorausgesetzt, es ist kein elisions, einen einzigen move-Konstruktor-Aufruf eliminiert mit pass durch rvalue.
- Der compiler hat eine bessere Chance zu elide kopiert/verschoben durch Wert übergeben. Kann das jemand untermauern diese Behauptung? Am besten mit einem link zu gcc.godbolt.org zeigt optimierte generierten code aus gcc/clang anstatt eine Zeile in der standard. Mein Versuch dieses zu zeigen war wohl nicht in der Lage, erfolgreich zu isolieren, die das Verhalten: https://godbolt.org/g/4yomtt
Nachtrag: warum bin ich einschränken von diesem problem so sehr?
- Keine überlastungen - falls es andere überlastungen, das würde übergehen in eine Diskussion über pass-by-value vs eine Reihe von überladungen, die const-Referenz-und rvalue-Referenz, an welcher Stelle der Satz von überlastungen ist offensichtlich effizienter und gewinnt. Dies ist sehr gut bekannt, und daher nicht interessant.
- Keine Vorlagen - ich bin nicht daran interessiert, wie die forwarding-Referenzen ins Bild passt. Wenn Sie eine Weiterleitung Referenz, rufen Sie std::forward sowieso. Das Ziel mit einer Spedition Referenz ist, an die Dinge, wie Sie Sie empfangen hat. Kopien sind nicht relevant, weil Sie nur pass ein lvalue statt. Es ist gut bekannt, und nicht interessant.
foo
müssen Sie der Besitzer desWidget
(aka keineconst Widget&
) - Wir reden hier nicht von nur-lese-Funktionen. Wenn die Funktion " schreibgeschützt war oder nicht brauchen, um eigenen oder verlängern die Lebensdauer derWidget
, dann ist die Antwort trivial wirdconst Widget&
, die wiederum ist gut bekannt, und nicht interessant. Ich habe auch erfahren Sie, warum wir nicht darüber reden wollen überlastungen.
- Warum nicht einfach
std::move
anstatt einen Vermittler kopieren? - Ich könnte das machen, wenn ich nicht beabsichtigen, verwenden Sie die variable nach. Der Punkt ist, dass, wenn ich brauche eine Kopie, es ist jetzt explicit. Das Beispiel geht von einer Kopie ist notwendig für einige Grund.
- Es hängt davon ab, was
foo
ist dabei mit dem parameter. Es ist ungewöhnlich für einen nicht-member-Funktion wie dieses zu brauchen, um sich den Besitz des Parameters. - Wenn Ihre Antwort würde variieren je nachdem, wie es verwendet wird, ich bin gespannt, wie würde Sie trennen. Das sagte Eigentums erfordert nicht die Speicherung auf dem heap/seine Lebensdauer wesentlich anders, nur, dass es eine Kopie beteiligten (oder, dass die Notwendigkeit für eine Kopie wird entfernt/optimierte Weg über einen Umzug). Es könnte sein, dass du das speichern der Werte in ein struct für später, könnte es sein, dass es ein iterator, die Sie brauchen, um voranzukommen.
- Diese beiden Schnittstellen sind nicht austauschbar, wie pass-by-value nimmt auch l-Werte. Also ich bin mir nicht sicher, ob diejenigen, die verglichen werden können, sinnvoll, ohne die Angabe von weiteren Einschränkungen für die Nutzung.
- Ich verstehe immer noch nicht deine Argumentation, dass
const Widget&
überlastungen. Sie sagen, mit den überlastungen ist effizienter und gewinnt, aber Sie wollen nicht zu betrachten, es sowieso? Und Sie sagen, Sie können nicht, verwenden Sie eineconst Widget&
überlastung, da die Funktion erfordert das "Eigentum" noch in den code, den Sie zeigen, es nimmt einem den Besitz einer Kopie, die Sie tun können, mit einemconst Widget&
überlastung. - Diese Frage ist sehr breit. Es macht einen großen Unterschied, ob die Funktion das Objekt ändern oder nicht, wenn Sie beschränken Sie es so, dass die Funktion immer das Objekt, ändert es machen würde, für immer mehr on-topic Antworten.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Ja, die übergabe per rvalue-Referenz bekam einen Punkt.
Ja, pass-by-value, bekam einen Punkt.
Aber auch das geben, übergabe per rvalue die Gelegenheit zu handhaben Ausnahme guaranty:
wenn
foo
wirft,widget
Wert ist nicht notwendig verbraucht.Move-only-Typen (wie
std::unique_ptr
), pass-by-value scheint die Regel zu sein (vor allem auch für deinen zweiten Punkt, und der erste Punkt ist nicht anwendbar sowieso).EDIT: standard-Bibliothek widerspricht meinen vorherigen Satz, eine
shared_ptr
's Konstruktor nimmtstd::unique_ptr<T, D>&&
.Für Arten, die sowohl kopieren/verschieben (wie
std::shared_ptr
), haben wir die Wahl, die Kohärenz mit den vorhergehenden Arten oder höherer explizit auf "kopieren".es sei denn, Sie wollen die Garantie, es gibt keine unerwünschte Kopie, würde ich verwenden Sie die pass-by-value für Kohärenz.Es sei denn, Sie wollen garantierte und/oder sofortige sinken, würde ich die übergabe per rvalue.
Für bestehenden code-Basis, ich würde die Konsistenz.
Es sei denn, der Typ ist ein bewegen-nur geben Sie normalerweise eine option, um pass by reference-to-const, und es scheint willkürlich zu machen, "nicht Teil der Diskussion", aber ich werde es versuchen.
Ich denke, dass die Wahl teilweise, hängt davon ab, was
foo
ist jetzt mit dem parameter.Muss die Funktion eine lokale Kopie
Sagen wir mal
Widget
ist ein iterator und Sie möchten, um Ihre eigenenstd::next
Funktion.next
braucht seine eigene Kopie Voraus und kehrt dann zurück. In diesem Fall ist die Wahl ist so etwas wie:vs
Ich denke-Wert ist hier besser. Aus der Signatur kannst du es sehen, ist eine Kopie. Wenn der Anrufer vermeiden will, eine Kopie, die Sie tun können, eine
std::move
und garantieren die variable verschoben wird, aber Sie kann immer noch passieren lvalues, wenn Sie wollen.Mit übergabe per rvalue-Referenz der Anrufer kann nicht garantieren, dass die variable verschoben wurde.
Move-Zuweisung eine Kopie
Lassen Sie uns sagen, Sie haben eine Klasse
WidgetHolder
:und Sie implementieren müssen, um eine
setWidget
member-Funktion. Ich nehme an, Sie haben bereits eine überlastung, übernimmt eine Referenz auf const:aber nach der Messung der Leistung, die Sie entscheiden, dass Sie brauchen, um eine Optimierung für die r-Werte. Sie haben dabei die Wahl, ihn zu ersetzen mit:
Oder überlastung mit:
Dieser ist ein wenig komplizierter. Es ist verlockend, wählen Sie " pass-by-value, weil es akzeptiert sowohl rvalues und lvalues, so brauchen Sie nicht zwei überladungen. Es ist jedoch unbedingt unter eine Kopie, so können Sie nicht nutzen alle vorhandenen Kapazitäten in den member-Variablen. Die übergabe per Referenz zu const, und übergeben Sie durch den r-Wert-Referenz überlastungen verwenden Zuordnung, ohne dass eine Kopie, die schneller sein
Bewegen-Konstrukt eine Kopie
Nun können sagen, Sie sind das schreiben der Konstruktor für
WidgetHolder
und nach wie vor haben Sie bereits implementiert einen Konstruktor eine Referenz auf const:und bevor du gemessen hast peformance und beschlossen, die Sie benötigen, um eine Optimierung für rvalues. Sie haben dabei die Wahl, ihn zu ersetzen mit:
Oder überlastung mit:
In diesem Fall die member-variable kann nicht über vorhandene Kapazitäten, denn dies ist der Konstruktor. Sie sind bewegen-constucting eine Kopie. Auch Konstruktoren nehmen oft viele Parameter, so kann es durchaus ein Schmerz zu schreiben, all die verschiedenen Permutationen von überlastungen zu optimieren, für r-Wert-Referenzen. Also in diesem Fall ist es eine gute Idee, pass-by-value, vor allem, wenn der Konstruktor nimmt viele dieser Parameter.
Vorbei
unique_ptr
Mit
unique_ptr
die Effizienz betrifft sind weniger wichtig, da ein Umzug ist so Billig und es besteht keine Kapazität. Wichtiger ist die Aussagekraft und Richtigkeit. Es gibt eine gute Diskussion darüber, wie das passierenunique_ptr
hier.next
obwohl es nur das zurückstellen der Kopie bis zur Rückkehr. Bezüglich "...es ist bereits bekannt, dass rvalue Parameter sind besser" ich habe versucht, ein Beispiel anzugeben (Konstruktor), wo der von-Wert ist besser.move-construct a copy
, warumWidgetHolder::WidgetHolder(Widget&& w) : widget(std:move(w))
ist nicht gut? Ich denke, dies sollte berücksichtigt werden, besser als pass-by-value, da wir die Umsetzung der Semantik von move-Konstrukt für widget-Halter: siehe Beispiel in en.cppreference.com/w/cpp/language/move_constructorWidgetHolder
wäre:WidgetHolder::WidgetHolder(WidgetHolder&&)
. Dies ist ein normaler Konstruktor, der passiert, zu nehmenWidget
. Es gibt nichts besonders falsch mit der übergabe durch die r-Wert-Referenz in diesem Fall aber, müssen Sie haben zwei überladungenWidgetHolder::WidgetHolder(Widget&& w)
undWidgetHolder::WidgetHolder(const Widget& w)
statt, ohne besonderen nutzen. Dies ist besonders ärgerlich, wenn Sie einen Konstruktor mit vielen Parametern, da brauchst du dann viele Permutationen von überlastungen.Was tun rvalue Verwendungen sagen, über eine Schnittstelle versus kopieren?
rvalue suggeriert dem Anrufer, dass die Funktion beide besitzen möchte den Wert und nicht die Absicht hat, lassen die Anrufer wissen, über änderungen, die er vorgenommen hat. Betrachten Sie das folgende (ich weiß, Sie sagte kein lvalue-Referenzen in Ihrem Beispiel, aber Bär mit mir):
Für einen anderen Weg, es zu betrachten:
Nun, wenn Widgets haben, um einzigartig zu bleiben (als tatsächliche widgets in GTK), dann die erste Möglichkeit nicht funktionieren kann. Die zweite, Dritte und vierte Option Sinn machen, weil es weiterhin nur eine richtige Darstellung der Daten. Eh, das ist, was diese Semantik zu mir sagen, wenn ich Sie sehe im code.
Nun, was die Effizienz: es hängt davon ab. rvalue Referenzen können eine Menge Zeit sparen, wenn Widget ist ein Zeiger auf ein Datenelement, dessen spitz-Inhalte können sehr groß sein (denken Sie an ein array). Da der Anrufer verwendet ein rvalue, Sie sagen, Sie kümmern sich nicht darum, was Sie geben sind Sie nicht mehr. Also, wenn Sie wollen, bewegen Sie den Anrufer Sie den Widget-Inhalt in das Widget, nehmen Sie nur Ihre Zeiger. Keine Notwendigkeit, sorgfältig kopieren Sie jedes element in der Daten-Struktur, deren Zeiger zeigt. Dies kann dazu führen, ziemlich gute Verbesserungen in der Geschwindigkeit (wieder, denke arrays). Aber wenn die Widget-Klasse nicht über eine solche Sache, dieser Vorteil ist nirgends zu sehen.
Hoffentlich das bekommt, was Sie Fragen; wenn nicht, kann ich das vielleicht erweitern/präzisieren sich die Dinge.
void foo(Car c)
Fall nicht sagen, wir haben jeder ein Auto, er sagt: "ich muss der Besitz eines Autos", es könnte eine neue (eine Kopie), es könnte sein, dass Sie sich entschieden haben, Sie brauchen nicht deins mehr und gab es mir. An diesem Punkt wird der Unterschied zwischen Fall 1 und 4 ist zeigen der Kopien an (sofern die Schnittstelle geht). Dann, und nur dann, macht die Frage interessant werden. Die anderen Fälle bleiben außerhalb des Bereichs.void foo(Car c)
Ergebnis wäre das gleiche, in das Ende, es ist nur eine erfundene Weise, es zu tun. Jane will ein Auto, Bob muss nicht sein mehr. Aber Jane will nicht zu verhängen, Bob, sodass Sie sich weigert, Bob ' persönliches Auto; nur will man es gerne. Bob, wollen, um loszuwerden, sein Auto, dann vorgibt, Sie zu erhalten, eine neue aber gibt wirklich Ihr sein. Das use-case-zwei Menschen nicht wirklich Zustimmen, was die Schnittstelle sein sollte. Auch erwähnte ich die anderen Fälle der Vollständigkeit halber 🙂Wenn Sie pass von rvalue-Referenz Objekt-Lebenszeiten kompliziert. Wenn der angerufene nicht bewegen, aus dem argument, die Zerstörung der argument verzögert. Ich denke, das ist interessant, in beiden Fällen.
Ersten, Sie haben eine RAII Klasse
Beim initialisieren
y
, die Ressource konnte noch gehalten werden, indemx
wennfn
bewegt sich nicht aus der rvalue-Referenz. In der pass-by-value-code, wir wissen, dassx
bewegt wird, undfn
releasesx
. Dies ist wahrscheinlich ein Fall, wo Sie möchten, um durch Wert übergeben, und der copy-Konstruktor würde wahrscheinlich gelöscht werden, so dass Sie nicht haben, um sorgen über die versehentliche Kopien.Zweite, wenn das argument ein großes Objekt und die Funktion bewegt sich nicht aus, die Lebensdauer der Vektoren Daten ist größer als im Falle von durch Wert übergeben.
In dem obigen Beispiel, wenn
fn1
undfn2
bewegen sich nicht ausx
, dann werden Sie am Ende mit allen Daten in allen Vektoren noch am Leben. Wenn Sie stattdessen durch Wert übergeben, wird nur die Letzte Vektor die Daten werden noch am Leben sein (vorausgesetzt, Vektoren move-Konstruktor löscht den Quellen-Vektor).template<typename T> typename std::remove_reference<T>::type move_fo_realzies(T&& x) { return std::move(x); }
. Das Problem kommt erst ins Spiel, wenn Siestd::move
stattmove_fo_realzies
... aber Leute die das letztere würde erfordern einige zusätzliche re-education.Eine Ausgabe, die nicht erwähnt in den anderen Antworten ist die Idee des exception-Sicherheit.
Im Allgemeinen, wenn die Funktion löst eine exception aus, im Idealfall würden wir gerne die starken Ausnahme-Garantie, was bedeutet, dass der Anruf hat keinen anderen Effekt, als wenn das die Ausnahme. Wenn der pass-by-value verwendet den move-Konstruktor, dann ist ein solcher Effekt ist im wesentlichen unvermeidbar. So eine rvalue-Referenz-argument kann überlegen sein in einigen Fällen. (Natürlich gibt es verschiedene Fälle, wo die starken Ausnahme-Garantie ist nicht erreichbar, entweder Weise, wie auch in verschiedenen Fällen, in denen die no-throw-Garantie ist so oder so. Also ist das nicht relevant, in 100% der Fälle. Aber es ist relevant, manchmal.)
Wahl zwischen by value und by-rvalue-ref -, nicht mit anderen überladungen, ist nicht sinnvoll.
Mit pass-by-value das eigentliche argument ein lvalue-Ausdruck.
Mit pass durch rvalue-ref das eigentliche argument ist ein rvalue.
Wenn die Funktion speichert eine Kopie des Arguments, dann eine vernünftige Wahl zwischen pass-by-value, und eine Reihe von überladungen mit pass-by-ref-auf-const und die übergabe per rvalue-ref. Für ein rvalue-Ausdruck als Tatsächliches argument der Satz von überlastungen vermeiden kann man verschieben. Es ist ein engineering-Darm-feeling Entscheidung, ob der micro-Optimierung lohnt sich die zusätzliche Komplexität und mit der Eingabe.