Was ist der Unterschied zwischen std :: move und std :: forward?

160

Ich habe das hier gesehen: Move Constructor ruft den Move Constructor der Basisklasse auf

Könnte jemand erklären:

  1. der Unterschied zwischen std::moveund std::forward, vorzugsweise mit einigen Codebeispielen?
  2. Wie man leicht darüber nachdenkt und wann man welche benutzt
aCuria
quelle
„Wie wäre es zu denken , einfach und verwenden , wenn die“ verwenden Sie , movewenn Sie wollen bewegen Wert, und forwardwenn Sie möchten , perfekte Weiterleitung. Dies ist hier keine Raketenwissenschaft;)
Nicol Bolas
move () führt eine bedingungslose Umwandlung durch, während forward () eine Umwandlung basierend auf dem übergebenen Parameter durchführt.
RaGa__M

Antworten:

159

std::moveNimmt ein Objekt und ermöglicht es Ihnen, es als temporär (einen Wert) zu behandeln. Obwohl dies keine semantische Anforderung ist, macht eine Funktion, die einen Verweis auf einen r-Wert akzeptiert, diesen normalerweise ungültig. Wenn Sie sehen std::move, wird angezeigt, dass der Wert des Objekts danach nicht mehr verwendet werden soll. Sie können jedoch einen neuen Wert zuweisen und ihn weiterhin verwenden.

std::forwardhat einen einzigen Anwendungsfall: Um einen Funktionsparameter mit Vorlagen (innerhalb der Funktion) in die Wertekategorie (lvalue oder rvalue) umzuwandeln, die der Aufrufer verwendet hat, um ihn zu übergeben. Auf diese Weise können rvalue-Argumente als rvalues ​​und lvalues ​​als lvalues ​​weitergegeben werden, ein Schema, das als "perfekte Weiterleitung" bezeichnet wird.

Zur Veranschaulichung :

void overloaded( int const &arg ) { std::cout << "by lvalue\n"; }
void overloaded( int && arg ) { std::cout << "by rvalue\n"; }

template< typename t >
/* "t &&" with "t" being template param is special, and  adjusts "t" to be
   (for example) "int &" or non-ref "int" so std::forward knows what to do. */
void forwarding( t && arg ) {
    std::cout << "via std::forward: ";
    overloaded( std::forward< t >( arg ) );
    std::cout << "via std::move: ";
    overloaded( std::move( arg ) ); // conceptually this would invalidate arg
    std::cout << "by simple passing: ";
    overloaded( arg );
}

int main() {
    std::cout << "initial caller passes rvalue:\n";
    forwarding( 5 );
    std::cout << "initial caller passes lvalue:\n";
    int x = 5;
    forwarding( x );
}

Wie Howard erwähnt, gibt es auch Ähnlichkeiten, da diese beiden Funktionen einfach in den Referenztyp umgewandelt werden. Außerhalb dieser speziellen Anwendungsfälle (die 99,9% des Nutzens von rvalue-Referenz-Casts abdecken) sollten Sie diese jedoch static_castdirekt verwenden und eine gute Erklärung für Ihre Aktivitäten schreiben.

Kartoffelklatsche
quelle
Ich bin nicht sicher, ob es richtig ist, dass std::forwardder einzige Anwendungsfall die perfekte Weiterleitung von Funktionsargumenten ist. Ich bin auf Situationen gestoßen, in denen ich andere Dinge wie Objektmitglieder perfekt weiterleiten möchte.
Geoff Romer
@GeoffRomer Mitglieder eines Parameters? Hast du ein beispiel
Potatoswatter
Ich habe ein Beispiel als separate Frage gepostet: stackoverflow.com/questions/20616958/…
Geoff Romer
Hmm, wenn ich das richtig verstehe, bedeutet das, dass ich eine einzelne Funktion forward () schreiben kann, wobei forward (5) mit 5 arbeitet, als hätte ich sie als Wert übergeben, während forward (x) mit x arbeitet, als ob ich sie übergeben hätte Referenz, ohne explizit eine Überladung schreiben zu müssen.
Iheanyi
@iheanyi Ja, das ist die Idee! Aber es muss eine Vorlage sein, nicht nur eine gewöhnliche Funktion.
Potatoswatter
63

Beides std::forwardund std::movesind nichts als Abgüsse.

X x;
std::move(x);

Das Obige wandelt den l-Wert-Ausdruck xvom Typ X in einen r-Wert-Ausdruck vom Typ X um (genauer gesagt einen x-Wert). movekann auch einen rwert akzeptieren:

std::move(make_X());

und in diesem Fall handelt es sich um eine Identitätsfunktion: Nimmt einen r-Wert vom Typ X und gibt einen r-Wert vom Typ X zurück.

Mit std::forwardkönnen Sie das Ziel bis zu einem gewissen Grad auswählen:

X x;
std::forward<Y>(x);

Wandelt den l-Wert-Ausdruck xvom Typ X in einen Ausdruck vom Typ Y um. Es gibt Einschränkungen, was Y sein kann.

Y kann eine zugängliche Basis von X sein oder ein Verweis auf eine Basis von X. Y kann X sein oder ein Verweis auf X. Man kann Lebenslauf-Qualifizierer nicht mit wegwerfen forward, aber man kann Lebenslauf-Qualifizierer hinzufügen. Y kann kein Typ sein, der lediglich von X konvertierbar ist, außer über eine zugängliche Basiskonvertierung.

Wenn Y eine lWertreferenz ist, ist das Ergebnis ein lWertausdruck. Wenn Y keine l-Wert-Referenz ist, ist das Ergebnis ein r-Wert-Ausdruck (genauer gesagt x-Wert).

forwardkann ein rvalue-Argument nur annehmen, wenn Y keine lvalue-Referenz ist. Das heißt, Sie können keinen Wert in einen Wert umwandeln. Dies ist aus Sicherheitsgründen so, da dies häufig zu baumelnden Referenzen führt. Aber es ist in Ordnung und erlaubt, einen Wert in einen Wert umzuwandeln.

Wenn Sie versuchen, Y für etwas anzugeben, das nicht zulässig ist, wird der Fehler zur Kompilierungszeit und nicht zur Laufzeit abgefangen.

Howard Hinnant
quelle
Kann ich dieses Objekt verwenden std::forward, nachdem ich ein Objekt mithilfe von perfekt an eine Funktion weitergeleitet habe ? Mir ist bewusst, dass es sich im Falle von um std::moveein undefiniertes Verhalten handelt.
Iammilind
1
In Bezug auf move: stackoverflow.com/a/7028318/576911 Für forward, wenn Sie in einem L - Wert, Ihre API übergeben sollten reagieren , als ob es einen L - Wert erhält. Normalerweise bedeutet dies, dass der Wert nicht geändert wird. Wenn es sich jedoch um einen nicht konstanten Wert handelt, hat Ihre API ihn möglicherweise geändert. Wenn Sie einen r-Wert übergeben, bedeutet dies normalerweise, dass Ihre API möglicherweise von diesem Wert verschoben wurde und daher stackoverflow.com/a/7028318/576911 angewendet wird .
Howard Hinnant
21

std::forwardwird verwendet, um einen Parameter genau so weiterzuleiten , wie er an eine Funktion übergeben wurde. Genau wie hier gezeigt:

Wann sollte std :: forward verwendet werden, um Argumente weiterzuleiten?

Die Verwendung std::movebietet ein Objekt als r-Wert an, um möglicherweise einem Verschiebungskonstruktor oder einer Funktion zu entsprechen, die r-Werte akzeptiert. Es macht das std::move(x)auch dann, wenn xes kein Wert für sich ist.

Bo Persson
quelle