Wie im Titel ausgedrückt, fragte ich mich, ob es einen gültigen Grund/Beispiel gibt, für die eine gegebene Klassenmethode, Move Konstruktor und move Zuweisungsoperator, oder free-Funktion sollte nehmen, als Eingabeparameter, a R-Wert-Referenz.Gibt es einen gültigen Grund, für den eine Methode eine R-Wert-Referenz als Eingabe nehmen sollte?
Gibt es einen gültigen Grund, für den eine Methode eine R-Wert-Referenz als Eingabe nehmen sollte?
Antwort
Es gibt zwei Möglichkeiten, ein Senkenargument zu modellieren.
Die erste ist durch Neben Wert annimmt:
void foo(std::string);
die zweite ist durch rvalue Referenz:
void foo(std::string&&);
mit der möglichen Variante einschließlich einer const&
Überlastungs Arbeit für den Anrufer zu vereinfachen.
inline void foo(std::string const& s){
auto tmp = s;
return foo(std::move(tmp));
}
Die take-sink-by-Wert hat einen zusätzlichen Overhead von einem einzigen std::move
über sie durch &&
Einnahme und const&
(oder Anrufer erfordern manuell einen nicht temporären Wert kopieren und in sich zu bewegen). Es erfordert nicht die 2. Überlast.
Also, wenn diese eine Bewegung ist es wert, Buchhaltung, unter nehmen Argumente durch const&
und &&
Sparen Sie einen Umzug. Plus, wenn Kopie ist teuer, können Sie es schwierig an der Aufruf-Website machen und so entmutigen.
Aber das ist nicht der einzige Grund. Manchmal möchten Sie feststellen, ob etwas ein R-Wert oder L-Wert ist, und nur kopieren, wenn es ein R-Wert ist.
Angenommen, wir hatten einen Bereichsadapter backwards
. backwards
nimmt einen geeigneten Bereich (etwas, das Sie for(:)
über, und deren Iteratoren kann umgekehrt werden) und gibt einen Bereich, der es rückwärts durchlaufen.
Naiv, alles, was Sie tun müssen, ist begin
und end
aus dem Quellbereich zu bekommen, dann Reverse-Iteratoren machen und speichern sie und bringt sie von Ihren eigenen begin
und end
Methoden.Leider
, das bricht:
std::vector<int> get_some_ints();
for(int x : backwards(get_some_ints())) {
std::cout << x << "\n";
}
da die Lebensdauer der temporären Rückkehr aus get_some_ints
wird nicht durch die Schleife for(:)
verlängert!
Das for(:)
erweitert grob:
{
auto&& __range_expression = backwards(get_some_ints());
auto __it = std::begin(__range_expression);
auto __end = std::end(__range_expression);
for (; __it != __end; ++__it) {
int x = *__it;
std::cout << x << "\n";
}
}
(Es gibt einige kleine Lügen über den Kindern erzählt, aber es ist nahe genug für diese Diskussion).
Insbesondere diese Zeile:
auto&& __range_expression = backwards(get_some_ints());
der Rückgabewert von backwards
wird Lebensdauer verlängert; aber die Lebensdauer seiner Argumente ist nicht!
Wenn also backwards
eine R const&
übernimmt, wird die vector
im Hintergrund vor der Schleife zerstört, und die beteiligten Iteratoren sind ungültig.
Also backwards
muss eine Kopie der vector
für den obigen Code, um gültig zu sein. Das ist unsere einzige Chance, den Vektor lange genug zu halten!
Auf der anderen Seite, in einem herkömmlichen Fall:
auto some_ints = get_some_ints();
for(int x : backwards(some_ints)) {
std::cout << x << "\n";
}
eine zusätzliche Kopie von some_ints
Speicherung wäre eine schreckliche Idee und ganz unerwartet.
In diesem Fall muss backwards
erkennen, ob sein Argument ein rvalue oder ein lvalue ist, und wenn es ein rvalue ist, muss es kopiert und im Rückgabewert gespeichert werden, und wenn es ein lvalue ist, muss es um entweder nur Iteratoren oder eine Referenz darauf zu speichern.
Vermeidung unnötiger Kopien, wenn Werte beibehalten werden. Ich beziehe mich gerne auf diese Funktionen als "Sink-Funktionen". Dies tritt sehr häufig auf, wenn Setter definiert werden.
class A
{
private:
std::string _s;
public:
void setS(const std::string& s) { _s = s; }
void setS(std::string&& s) { _s = std::move(s); }
};
int main()
{
A a;
std::string s{"some long string ......."};
a.setS(s); // copies and retains `s`
a.setS(std::move(s)); // moves and retains `s`
}
Ich denke eher als 'std :: string && s' mit' const std :: string & 'zu vergleichen, es ist interessanter zu vergleichen es mit nur "std :: string s" nach Wert. Das ist normalerweise die Alternative, die Leute zu 'std :: string &&' im Sinn haben. –
Siehe auch: http://stackoverflow.com/questions/37935393/pass-by-value-vs-pass-by-rvalue-reference –
@ChrisBeck: Ja, aber es ist nicht so effizient wie mit zwei Überladungen. Es gibt eine Menge Diskussion darüber online: http://blogs.microsoft.co.il/sasha/2014/08/21/c-sink-parameter-passing/ –
Manchmal wollen Sie Eigentum an so etwas wie ein std::vector
groß nehmen, aber Sie eine Kopie von Unfall vermeiden machen.
von nur ein r-Referenzwert Bereitstellung eines Anrufers überlasten, die eine Kopie übergeben will, muss dies ausdrücklich tun:
class DataHolder {
std::vector<double> a;
std::vector<int> b;
public:
DataHolder(std::vector<double>&& a, std::vector<int>&& b) : a(a), b(b) {}
};
auto a1 = makeLotsDoubles();
auto b1 = makeLotsInts();
DataHolder holder(std::move(a1), std::move(b1)); // No copies. Good.
auto a2 = makeLotsDoubles();
auto b2 = makeLotsInts();
DataHolder holder(a2, b2) // Forgot to move, compiler error.
Wenn stattdessen hatten Sie Pass-by-Wert verwendet, dann, wenn Sie vergessen, Verwenden Sie std::move
auf einem Lvalue, dann wird eine Kopie erstellt.
Es ist nützlich, ein Objekt nicht kopierbar in die Klasse zu übertragen. Einige Objekte haben keinen Kopierkonstruktor, was bedeutet, dass sie nicht als Wert übergeben werden können. Man sieht dies mit RAII, wo das Erstellen einer Kopie eine neue Menge von Ressourcen erhalten würde. Oder im Allgemeinen, wenn der Kopierkonstruktor und/oder Destruktor Nebenwirkungen außerhalb des (de) konstruierten Objekts haben. Das Erstellen einer Kopie ist möglicherweise nicht nur ineffizient, wie das Kopieren einer std :: string, aber ziemlich unmöglich.
Eine Möglichkeit, ein Objekt wie dieses zu übergeben, besteht darin, es als Zeiger zu übergeben. Zum Beispiel:
// Creates a file on construction, deletes on destruction
class File;
{
// Create files
File* logfile1 = new File("name1");
File logfile2("name2");
// Give files to logger to use
logger.add_output(logfile1); // OK
logger.add_output(&logfile2); // BAD!
}
File
hat keinen Copykonstruktor, als eine Kopie würde erstellen und die gleiche Datei wie das Original löschen, die keinen Sinn macht. Daher übergeben wir einen Zeiger auf die Datei an das Objekt logger
, um das Kopieren zu vermeiden. Mit logfile1
funktioniert es in Ordnung, vorausgesetzt natürlich, dass logger
die Datei löscht, wenn es damit fertig ist. Aber logfile2
hat zwei große Probleme. Einer ist, dass logger
es nicht löschen kann, da es nicht mit new
zugewiesen wurde. Der andere ist, dass logfile2
zerstört wird, die Datei gelöscht wird und der Zeiger auf ihn ungültig wird, gespeichert in logger
, wenn logfile2
den Gültigkeitsbereich am Ende des Blocks verlässt.
Wir könnten File
einen Move-Konstruktor geben, der den Besitz der Datei auf die Zieldatei überträgt und die Quelldatei "leer" macht. Jetzt können wir den obigen Code so schreiben, dass er funktioniert.
{
// Create files
File* logfile1 = new File("name1");
File logfile2("name2");
// Give files to logger to use
logger.add_output(std::move(*logfile1));
logger.add_output(std::move(logfile2));
logger.add_output(File("name3"));
delete logfile1;
}
Wir können nun eine File
mit neuen oder als lokales Objekt oder sogar als unbenannte pr-Wert schaffen. Die Verwendung von std::move
macht auch auf der Abrufseite deutlich, dass das Eigentum an der File
auf logger
übertragen wird. Wenn die Besitzübertragung des Zeigers nicht klar angezeigt wird, hängt es von logger.add_output()
ab, die Semantik zu dokumentieren und diese Dokumentation zu überprüfen.
Ein Objekt ohne Kopierkonstruktor kann nach Wert übergeben werden, wenn es einen Move-Konstruktor hat: http://melpon.org/wandbox/permlink/YYmU43P1ZLKlLJKx –
- 1. Gibt es einen gültigen Grund für die Code-Duplizierung?
- 2. Gibt es einen Grund Standardalgorithmen nehmen Lambdas nach Wert?
- 3. Gibt es eine schlechte Seite für die Verwendung einer Struktur als Eingabe für eine Methode?
- 4. Gibt es einen Grund für die Abwertung der Shutdown-Methode?
- 5. Gibt es eine Methode für Pull-Anführungszeichen?
- 6. Gibt es einen Grund, eine Singleton-Bean zu proxysen?
- 7. Gibt es eine Möglichkeit, den Grund herauszufinden, wenn ftp_put fehlschlägt?
- 8. Gibt es einen Grund, den Vorlagentyp explizit anzugeben?
- 9. Gibt es einen Grund, "var" nicht aufzugeben?
- 10. Gibt es eine HasNext-Methode für einen IEnumerator?
- 11. Gibt es eine umgekehrte Methode für .charAt()?
- 12. Gibt es eine "setSharedElementsUseOverlay()" -Methode für Fragmentübergänge?
- 13. Gibt es einen guten Grund, Metaprogrammierung zu verwenden, um den Rückgabetyp einer Methode zu ändern?
- 14. Gibt es einen Grund, eine Methode virtuell ohne Vererbung zu deklarieren?
- 15. wie eine Zeile aus einem Text nehmen als Eingabe für eine Funktion Python
- 16. Es gibt einen Grund, den wiederaufsetzbaren Upload zu verwenden, wenn ich eine stabile Verbindung habe?
- 17. Gibt es einen Grund, warum einige Bewegungsbefehle von VIM auf eine Zeile beschränkt sind?
- 18. Gibt es eine Positionseigenschaft für einen Textfeldkopf?
- 19. Gibt es einen berechtigten Grund, new und einen Konstruktor für eine Zahlenklasse in Java zu verwenden?
- 20. Nehmen Sie zwei Werte als eine Eingabe in R Liste
- 21. Gibt es eine CapitalizeFirstLetter-Methode?
- 22. Gibt es einen bestimmten Grund, warum Menschen eine Bildgröße von 224x224 für Imaginexperimente wählen?
- 23. Gibt es eine jQuery-Unfokus-Methode?
- 24. Ist es eine Methode oder eine Klasse?
- 25. gibt es eine Schnelltaste für den schnellen
- 26. Gibt es in Python eine "Wildcard-Methode"?
- 27. Gibt es einen guten Grund für Javascript inline zu sein
- 28. Gibt es eine Möglichkeit, eine Methode als Parameter an eine Methode zu übergeben?
- 29. WindowManager.LayoutParams Warum gibt es eine setTitle-Methode?
- 30. Gibt es einen wirklichen Grund, Optional.of() zu verwenden?
Warum würden Sie freie Funktionen ausschließen, aber keine Mitgliedsfunktionen? Was ist der Unterschied? –
Was ist mit Klassen, die Ressourcen verwalten sollen? Es ist sinnvoll, dass sie in der Lage sind, eine Instanz ihrer verwalteten Ressource nach rvalue-Referenz zu übernehmen, sodass Sie ihnen direkt das Ergebnis einer Operation übergeben können, die diese Ressource zurückgibt, ohne einen Zwischenhändler zu benötigen. –
@ChristianHackl Ich habe keine Free-Funktionen ausgeschlossen, ich habe nur den Move-Konstruktor und den Zuweisungsoperator ausgeschlossen, da es sich um "triviale" Beispiele handelt. – FdeF