it-swarm.com.de

Was ist std :: move () und wann sollte es verwendet werden?

  1. Was ist es?
  2. Was tut es?
  3. Wann sollte es angewendet werden?

Gute Links sind erwünscht.

559
Basilevs

Wikipedia-Seite zu C++ 11 R-Wert-Referenzen und Move-Konstruktoren

  1. In C++ 11 können Objekte neben Kopierkonstruktoren auch Verschiebungskonstruktoren haben.
    (Zusätzlich zu den Zuweisungsoperatoren für das Kopieren verfügen sie über Zuweisungsoperatoren für das Verschieben.)
  2. Der move-Konstruktor wird anstelle des copy-Konstruktors verwendet, wenn das Objekt den Typ "rvalue-reference" (Type &&) hat.
  3. std::move() ist eine Umwandlung, die eine r-Wert-Referenz auf ein Objekt erzeugt, um das Verschieben von diesem zu ermöglichen.

Es ist eine neue C++ - Methode, um Kopien zu vermeiden. Beispielsweise könnte ein std::vector mit einem Verschiebungskonstruktor seinen internen Zeiger auf Daten auf das neue Objekt kopieren und das verschobene Objekt in einem falschen Zustand belassen, ohne alle Daten zu kopieren. Dies wäre C++ - gültig.

Versuchen Sie, nach Bewegungssemantik, Wert und perfekter Weiterleitung zu suchen.

236
Scharron

1. "Was ist das?"

Während std::move() technisch eine Funktion ist - ich würde sagen , ist es nicht wirklich eine Funktion . Es ist eine Art Konverter zwischen den Arten, wie der Compiler den Wert eines Ausdrucks betrachtet.

2. "Was macht es?"

Als erstes ist zu beachten, dass std::move() eigentlich nichts bewegt . Es konvertiert einen Ausdruck von lvalue (wie eine benannte Variable) in xvalue . Ein x-Wert teilt dem Compiler mit:

Sie können mich plündern, alles, was ich in der Hand habe, bewegen und woanders verwenden (da ich sowieso bald zerstört werde) ".

mit anderen Worten, wenn Sie std::move(x) verwenden, können Sie dem Compiler erlauben, x auszuschalten. Wenn also x beispielsweise einen eigenen Puffer im Speicher hat - nach std::move()ing kann der Compiler stattdessen ein anderes Objekt besitzen.

Sie können auch von einem Wert (z. B. einem temporären Wert, den Sie herumreichen) wechseln, dies ist jedoch selten nützlich.

3. "Wann sollte es verwendet werden?"

Eine andere Möglichkeit, diese Frage zu stellen, ist "Wofür kann ich die Ressourcen eines vorhandenen Objekts ausschlachten?". Nun, wenn Sie Anwendungscode schreiben, werden Sie wahrscheinlich nicht viel mit temporären Objekten herumspielen, die vom Compiler erstellt wurden. Sie tun dies also hauptsächlich an Stellen wie Konstruktoren, Operatormethoden, Funktionen wie Standardbibliotheksalgorithmen usw., an denen Objekte häufig automatisch erstellt und zerstört werden. Das ist natürlich nur eine Faustregel.

Eine typische Verwendung besteht darin, Ressourcen von einem Objekt auf ein anderes zu verschieben, anstatt sie zu kopieren. @Guillaume verweist auf diese Seite , das ein einfaches kurzes Beispiel enthält: Tauschen von zwei Objekten mit weniger Kopieraufwand.

template <class T>
swap(T& a, T& b) {
    T tmp(a);   // we now have two copies of a
    a = b;      // we now have two copies of b (+ discarded a copy of a)
    b = tmp;    // we now have two copies of tmp (+ discarded a copy of b)
}

mit move können Sie die Ressourcen austauschen, anstatt sie zu kopieren:

template <class T>
swap(T& a, T& b) {
    T tmp(std::move(a));
    a = std::move(b);   
    b = std::move(tmp);
}

Denken Sie daran, was passiert, wenn T beispielsweise vector<int> der Größe n ist. In der ersten Version lesen und schreiben Sie 3 * n Elemente, in der zweiten Version lesen und schreiben Sie im Grunde nur die 3 Zeiger auf die Puffer der Vektoren. Natürlich muss die Klasse T wissen, wie man umzieht. Ihre Klasse sollte einen Verschiebungszuweisungsoperator und einen Verschiebungskonstruktor für die Klasse T haben, damit dies funktioniert.

174
einpoklum

Sie können move verwenden, wenn Sie den Inhalt eines Objekts an einen anderen Ort "übertragen" müssen, ohne eine Kopie anzufertigen (d. H. Der Inhalt wird nicht dupliziert, aus diesem Grund könnte er für einige nicht kopierbare Objekte verwendet werden, z. B. unique_ptr). Es ist auch möglich, dass ein Objekt mit std :: move den Inhalt eines temporären Objekts übernimmt, ohne eine Kopie anzufertigen (und viel Zeit zu sparen).

Dieser Link hat mir wirklich geholfen:

http://thbecker.net/articles/rvalue_references/section_01.html

Es tut mir leid, wenn meine Antwort zu spät kommt, aber ich habe auch nach einem guten Link für den std :: move gesucht und die obigen Links etwas "nüchtern" gefunden.

Dies legt den Schwerpunkt auf die Referenz von r-Werten, in welchem ​​Kontext Sie sie verwenden sollten, und ich denke, es ist detaillierter, deshalb wollte ich diesen Link hier teilen.

134
Guillaume

F: Was ist std::move?

A: std::move() ist eine Funktion aus der C++ - Standardbibliothek zum Umwandeln in eine R-Wert-Referenz.

Vereinfacht ausgedrückt ist std::move(t) äquivalent zu:

static_cast<T&&>(t);

Ein rWert ist ein temporärer Wert, der nicht über den Ausdruck hinaus bestehen bleibt, der ihn definiert, z. B. ein Zwischenfunktionsergebnis, das niemals in einer Variablen gespeichert wird.

int a = 3; // 3 is a rvalue, does not exist after expression is evaluated
int b = a; // a is a lvalue, keeps existing after expression is evaluated

Eine Implementierung für std :: move () ist in N2027: "Eine kurze Einführung in Rvalue-Referenzen" wie folgt angegeben:

template <class T>
typename remove_reference<T>::type&&
std::move(T&& a)
{
    return a;
}

Wie Sie sehen, gibt std::moveT&& zurück, unabhängig davon, ob der Aufruf mit einem Wert (T), einem Referenztyp (T&) oder einem Referenzwert (T&&) erfolgt. .

F: Was macht es?

A: Als Cast macht er zur Laufzeit nichts. Es ist nur zum Zeitpunkt der Kompilierung relevant, dem Compiler mitzuteilen, dass Sie die Referenz weiterhin als Wert betrachten möchten.

foo(3 * 5); // obviously, you are calling foo with a temporary (rvalue)

int a = 3 * 5;
foo(a);     // how to tell the compiler to treat `a` as an rvalue?
foo(std::move(a)); // will call `foo(int&& a)` rather than `foo(int a)` or `foo(int& a)`

Was es nicht tut :

  • Kopieren Sie das Argument
  • Rufen Sie den Kopierkonstruktor auf
  • Ändern Sie das Argumentobjekt

F: Wann sollte es verwendet werden?

A: Sie sollten std::move verwenden, wenn Sie Funktionen aufrufen möchten, die die Verschiebungssemantik mit einem Argument unterstützen, das kein R-Wert ist (temporärer Ausdruck).

Daraus ergeben sich für mich folgende Anschlussfragen:

  • Was ist Bewegungssemantik? Die Verschieben-Semantik ist im Gegensatz zur Kopieren-Semantik eine Programmiertechnik, bei der die Elemente eines Objekts durch "Übernehmen" initialisiert werden, anstatt die Elemente eines anderen Objekts zu kopieren. Eine solche Übernahme ist nur bei Zeigern und Ressourcenhandles sinnvoll, die kostengünstig übertragen werden können, indem der Zeiger oder das Ganzzahlhandle anstelle der zugrunde liegenden Daten kopiert werden.

  • Welche Klassen und Objekte unterstützen die Bewegungssemantik? Es liegt an Ihnen als Entwickler, die Verschiebungssemantik in Ihren eigenen Klassen zu implementieren, wenn diese davon profitieren würden, ihre Mitglieder zu übertragen, anstatt sie zu kopieren. Sobald Sie die Verschiebungssemantik implementiert haben, profitieren Sie direkt von der Arbeit vieler Bibliotheksprogrammierer, die Unterstützung für die effiziente Behandlung von Klassen mit der Verschiebungssemantik hinzugefügt haben.

  • Warum kann der Compiler es nicht selbst herausfinden? Der Compiler kann nicht einfach eine andere Überladung einer Funktion aufrufen, es sei denn, Sie sagen dies. Sie müssen dem Compiler bei der Auswahl helfen, ob die reguläre oder die verschobene Version der Funktion aufgerufen werden soll.

  • In welchen Situationen möchte ich dem Compiler mitteilen, dass eine Variable als Wert behandelt werden soll? Dies tritt höchstwahrscheinlich in Vorlagen- oder Bibliotheksfunktionen auf, bei denen Sie wissen, dass ein Zwischenergebnis gerettet werden kann.

52

std :: move selbst macht nicht viel. Ich dachte, dass es den verschobenen Konstruktor für ein Objekt nennt, aber es führt wirklich nur eine Typumwandlung durch (Umwandlung einer lvalue-Variablen in einen rvalue, so dass die Variable als Argument an einen move-Konstruktor oder einen Zuweisungsoperator übergeben werden kann).

Daher wird std :: move nur als Vorläufer für die Verwendung der Verschiebungssemantik verwendet. Die Verschiebungssemantik ist im Wesentlichen eine effiziente Methode für den Umgang mit temporären Objekten.

Betrachte Objekt A = B + C + D + E + F;

Dies ist ein gut aussehender Code, aber E + F erzeugt ein temporäres Objekt. Dann erzeugt D + Temp ein anderes temporäres Objekt und so weiter. In jedem normalen "+" Operator einer Klasse treten tiefe Kopien auf.

Zum Beispiel

Object Object::operator+ (const Object& rhs) {
    Object temp (*this);
    // logic for adding
    return temp;
}

Die Erstellung des temporären Objekts in dieser Funktion ist nutzlos - diese temporären Objekte werden am Ende der Zeile ohnehin gelöscht, da sie den Gültigkeitsbereich verlassen.

Wir können vielmehr die Verschiebungssemantik verwenden, um die temporären Objekte zu "plündern" und so etwas wie zu tun

 Object& Object::operator+ (Object&& rhs) {
     // logic to modify rhs directly
     return rhs;
 }

So vermeiden Sie unnötige tiefe Kopien. Unter Bezugnahme auf das Beispiel ist der einzige Teil, in dem tiefes Kopieren stattfindet, jetzt E + F. Der Rest verwendet die Verschiebungssemantik. Der Verschiebungskonstruktor oder Zuweisungsoperator muss ebenfalls implementiert werden, um das Ergebnis A zuzuweisen.

30
user929404

"Was ist das?" und "Was macht es?" wurde oben erklärt.

Ich werde ein Beispiel für "wann es verwendet werden sollte" geben.

Zum Beispiel haben wir eine Klasse mit vielen Ressourcen wie einem großen Array.

class ResHeavy{ //  ResHeavy means heavy resource
    public:
        ResHeavy(int len=10):_upInt(new int[len]),_len(len){
            cout<<"default ctor"<<endl;
        }

        ResHeavy(const ResHeavy& rhs):_upInt(new int[rhs._len]),_len(rhs._len){
            cout<<"copy ctor"<<endl;
        }

        ResHeavy& operator=(const ResHeavy& rhs){
            _upInt.reset(new int[rhs._len]);
            _len = rhs._len;
            cout<<"operator= ctor"<<endl;
        }

        ResHeavy(ResHeavy&& rhs){
            _upInt = std::move(rhs._upInt);
            _len = rhs._len;
            rhs._len = 0;
            cout<<"move ctor"<<endl;
        }

    // check array valid
    bool is_up_valid(){
        return _upInt != nullptr;
    }

    private:
        std::unique_ptr<int[]> _upInt; // heavy array resource
        int _len; // length of int array
};

Testcode:

void test_std_move2(){
    ResHeavy rh; // only one int[]
    // operator rh

    // after some operator of rh, it becomes no-use
    // transform it to other object
    ResHeavy rh2 = std::move(rh); // rh becomes invalid

    // show rh, rh2 it valid
    if(rh.is_up_valid())
        cout<<"rh valid"<<endl;
    else
        cout<<"rh invalid"<<endl;

    if(rh2.is_up_valid())
        cout<<"rh2 valid"<<endl;
    else
        cout<<"rh2 invalid"<<endl;

    // new ResHeavy object, created by copy ctor
    ResHeavy rh3(rh2);  // two copy of int[]

    if(rh3.is_up_valid())
        cout<<"rh3 valid"<<endl;
    else
        cout<<"rh3 invalid"<<endl;
}

ausgabe wie folgt:

default ctor
move ctor
rh invalid
rh2 valid
copy ctor
rh3 valid

Wir können sehen, dass std::move mit move constructor die Transformation von Ressourcen vereinfacht.

Wo sonst ist std::move nützlich?

std::move kann auch nützlich sein, wenn Sie ein Array von Elementen sortieren. Bei vielen Sortieralgorithmen (wie Auswahlsortierung und Blasensortierung) werden Elementpaare ausgetauscht. Bisher mussten wir für den Austausch auf die Kopiersemantik zurückgreifen. Jetzt können wir die effizientere Bewegungssemantik verwenden.

Es kann auch nützlich sein, wenn wir die von einem Smart Pointer verwalteten Inhalte auf einen anderen verschieben möchten.

Zitiert:

7
Jayhello