In dit geval
struct Foo {};
Foo meh() {
return std::move(Foo());
}
Ik ben er vrij zeker van dat de verplaatsing niet nodig is, omdat de nieuw gemaakte Foo
een x-waarde zal zijn.
Maar wat in dit soort gevallen?
struct Foo {};
Foo meh() {
Foo foo;
//do something, but knowing that foo can safely be disposed of
//but does the compiler necessarily know it?
//we may have references/pointers to foo. how could the compiler know?
return std::move(foo); //so here the move is needed, right?
}
Daar is de verhuizing nodig, neem ik aan?
Antwoord 1, autoriteit 100%
In het geval van return std::move(foo);
is de move
overbodig vanwege 12.8/32:
Wanneer aan de criteria voor het weglaten van een kopieerbewerking wordt voldaan of zou zijn
met uitzondering van het feit dat het bronobject een functieparameter is,
en het te kopiëren object wordt aangeduid met een lvalue, overload
resolutie om de constructor voor de kopie te selecteren wordt eerst uitgevoerd als
als het object werd aangeduid met een rwaarde.
return foo;
is een geval van NRVO, dus kopiëren is toegestaan. Foo
is een waarde. Dus de constructor die is geselecteerd voor de “kopie” van Foo
naar de geretourneerde waarde van meh
moet de move-constructor zijn als die bestaat.
Het toevoegen van move
heeft echter wel een potentieel effect: het voorkomt dat de zet wordt weggelaten, omdat return std::move(foo);
nietkomt in aanmerking voor NRVO.
Voor zover ik weet, bevat 12.8/32 de enigevoorwaarden waaronder een kopie van een lvalue kan worden vervangen door een zet. Het is de compiler in het algemeen niet toegestaan om te detecteren dat een l-waarde ongebruikt is na de kopie (bijvoorbeeld met behulp van DFA), en de wijziging op eigen initiatief aan te brengen. Ik neem hier aan dat er een waarneembaar verschil is tussen de twee — als het waarneembare gedrag hetzelfde is, dan is de “als-als”-regel van toepassing.
Dus, om de vraag in de titel te beantwoorden, gebruik std::move
op een retourwaarde wanneer u wilt dat deze wordt verplaatst en deze toch niet wordt verplaatst. Dat is:
- je wilt dat het wordt verplaatst, en
- het is een waarde, en
- het komt niet in aanmerking voor kopieerelisie, en
- het is niet de naam van een functieparameter op basis van waarde.
Aangezien dit nogal onhandig is en zetten meestalgoedkoop zijn, zou je kunnen zeggen dat je dit in niet-sjablooncode kunt vereenvoudigen. Gebruik std::move
wanneer:
- je wilt dat het wordt verplaatst, en
- het is een waarde, en
- je kunt je er geen zorgen over maken.
Door de vereenvoudigde regels te volgen, offer je wat bewegingselisie op. Voor typen zoals std::vector
die goedkoop te verplaatsen zijn, zul je het waarschijnlijk nooit merken (en als je het wel merkt, kun je optimaliseren). Voor typen zoals std::array
die duur zijn om te verplaatsen, of voor sjablonen waarvan u geen idee heeft of verhuizingen goedkoop zijn of niet, is de kans groter dat u zich er zorgen over maakt.
Antwoord 2, autoriteit 27%
De verplaatsing is in beide gevallen niet nodig. In het tweede geval is std::move
overbodig omdat je een lokale variabele op waarde retourneert, en de compiler zal begrijpen dat, aangezien je die lokale variabele niet meer gaat gebruiken, het verplaatst van in plaats van gekopieerd.
Antwoord 3, autoriteit 20%
Als bij een retourwaarde de retourexpressie rechtstreeks verwijst naar de naam van een lokale lvalue (d.w.z. op dit punt een xwaarde), is de std::move
niet nodig. Aan de andere kant, als de return-expressie nietde identifier is, wordt deze niet automatisch verplaatst, dus je zou bijvoorbeeld de expliciete std::move
hierin nodig hebben geval:
T foo(bool which) {
T a = ..., b = ...;
return std::move(which? a : b);
// alternatively: return which? std::move(a), std::move(b);
}
Als je een benoemde lokale variabele of een tijdelijke expressie rechtstreeks retourneert, moet je de expliciete std::move
vermijden. De compiler moet(en zal in de toekomst) in die gevallen automatisch worden verplaatst, en het toevoegen van std::move
kan andere optimalisaties beïnvloeden.
Antwoord 4, autoriteit 15%
Er zijn veel antwoorden over wanneer het niet moet worden verplaatst, maar de vraag is “wanneer moet het worden verplaatst?”
Hier is een gekunsteld voorbeeld van wanneer het moet worden gebruikt:
std::vector<int> append(std::vector<int>&& v, int x) {
v.push_back(x);
return std::move(v);
}
dat wil zeggen, als je een functie hebt die een rvalue-referentie nodig heeft, deze wijzigt en er vervolgens een kopie van teruggeeft. (In c++20gedrag verandert hier) In de praktijk is dit ontwerp bijna altijd beter:
std::vector<int> append(std::vector<int> v, int x) {
v.push_back(x);
return v;
}
waarmee u ook niet-rvalue parameters kunt gebruiken.
Kortom, als je een rvalue-referentie hebt binnen een functie die je wilt retourneren door te verplaatsen, moet je std::move
aanroepen. Als je een lokale variabele hebt (of het nu een parameter is of niet), retourneer je deze impliciet move
s (en deze impliciete zet kan worden weggelaten, terwijl een expliciete zet dat niet kan). Als je een functie of bewerking hebt die lokale variabelen nodig heeft en een verwijzing naar die lokale variabele retourneert, moet je std::move
gebruiken om de verplaatsing te laten plaatsvinden (bijvoorbeeld de trinaire ?:
operator).
Antwoord 5
Een C++-compiler is gratis te gebruiken std::move(foo)
:
- als bekend is dat
Foo
aan het einde van zijn levensduur is, en - het impliciete gebruik van
std::move
heeft geen enkel effect op de semantiek van de C++-code, behalve de semantische effecten die zijn toegestaan door de C++-specificatie.
Het hangt af van de optimalisatiemogelijkheden van de C++-compiler of deze in staat is om te berekenen welke transformaties van f(foo); foo.~Foo();
naar f(std::move(foo)); foo.~Foo();
zijn winstgevend in termen van prestaties of geheugenverbruik, terwijl ze zich houden aan de C++-specificatieregels.
Conceptueelgesproken, zijn C++-compilers voor het jaar 2017, zoals GCC 6.3.0, in staat om deze code te optimaliseren:
Foo meh() {
Foo foo(args);
foo.method(xyz);
bar();
return foo;
}
in deze code:
void meh(Foo *retval) {
new (retval) Foo(arg);
retval->method(xyz);
bar();
}
die het aanroepen van de copy-constructor en de destructor van Foo
vermijdt.
C++-compilers van het jaar 2017, zoals GCC 6.3.0, kunnen niet in staat zijn omdeze codes te optimaliseren:
Foo meh_value() {
Foo foo(args);
Foo retval(foo);
return retval;
}
Foo meh_pointer() {
Foo *foo = get_foo();
Foo retval(*foo);
delete foo;
return retval;
}
in deze codes:
Foo meh_value() {
Foo foo(args);
Foo retval(std::move(foo));
return retval;
}
Foo meh_pointer() {
Foo *foo = get_foo();
Foo retval(std::move(*foo));
delete foo;
return retval;
}
wat betekent dat een programmeur voor het jaar 2017 dergelijke optimalisaties expliciet moet specificeren.