Wat is std::move(), en wanneer moet het worden gebruikt?

Goede links worden gewaardeerd.


Antwoord 1, autoriteit 100%

Wikipedia-pagina op C++11 R-waardereferenties en verplaatsconstructors

  1. In C++11 kunnen objecten naast kopieerconstructors ook move-constructors hebben.
    (En naast toewijzingsoperatoren voor kopiëren, hebben ze toewijzingsoperatoren voor verplaatsen.)
  2. De move-constructor wordt gebruikt in plaats van de copy-constructor, als het object het type “rvalue-reference” heeft (Type &&).
  3. std::move()is een cast die een rvalue-referentie naar een object produceert, om het verplaatsen ervan mogelijk te maken.

Het is een nieuwe C++ manier om kopieën te vermijden. Als u bijvoorbeeld een move-constructor gebruikt, kan een std::vectorzijn interne aanwijzer naar gegevens naar het nieuwe object kopiëren, waardoor het verplaatste object in een verplaatste-status blijft en dus niet alle gegevens kopieert. Dit zou C++-geldig zijn.

Probeer eens te googlen op verplaatsingssemantiek, rvalue, perfect forwarding.


Antwoord 2, autoriteit 92%

1. “Wat is er?”

Hoewel std::move()technisch gezien een functie is, zou ik zeggen het is echtgeen functie. Het is een soort convertertussen manieren waarop de compiler de waarde van een expressie in overweging neemt.

2. “Wat doet het?”

Het eerste dat opvalt is dat std::move()niets verplaatst. Het verandert een uitdrukking van een lvalue(zoals een benoemde variabele) in een xvalue. Een x-waarde vertelt de compiler:

Je kunt me plunderen, verplaatsenalles wat ik vasthoud en het ergens anders gebruiken (aangezien ik toch binnenkort vernietigd zal worden)”.

met andere woorden, als je std::move(x)gebruikt, sta je de compiler toe om xte kannibaliseren. Dus als xbijvoorbeeld zijn eigen buffer in het geheugen heeft – na std::move()ing kan de compiler er in plaats daarvan eigenaar van worden door een ander object.

Je kunt ook overstappen van een prvalue(zoals een tijdelijk passeert), maar dit is zelden nuttig.

3. “Wanneer moet het worden gebruikt?”

Een andere manier om deze vraag te stellen is: “Waarvoor zou ik de bronnen van een bestaand object kannibaliseren?” Nou, als je applicatiecode schrijft, zou je waarschijnlijk niet veel rommelen met tijdelijke objecten die door de compiler zijn gemaakt. Dus je zou dit voornamelijk doen in plaatsen zoals constructors, operatormethoden, standaard-bibliotheek-algoritme-achtige functies enz. Waar objecten vaak automagisch worden gemaakt en vernietigd. Dat is natuurlijk maar een vuistregel.

Een typisch gebruik is het ‘verplaatsen’ van bronnen van het ene object naar het andere in plaats van te kopiëren. @Guillaume linkt naar deze paginamet een duidelijk kort voorbeeld: twee objecten verwisselen met minder kopiëren .

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)
}

door verplaatsen te gebruiken, kunt u de bronnen verwisselen in plaats van ze te kopiëren:

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

Denk aan wat er gebeurt als Tbijvoorbeeld vector<int>van grootte n is. In de eerste versie lees en schrijf je 3*n elementen, in de tweede versie lees en schrijf je in feite alleen de 3 pointers naar de buffers van de vectoren, plus de grootte van de 3 buffers. Natuurlijk moet klas Tweten hoe ze moeten verhuizen; je klas moet een operator voor verplaatsingen en een constructor voor klasse Thebben om dit te laten werken.


Antwoord 3, autoriteit 43%

U kunt move gebruiken wanneer u de inhoud van een object ergens anders moet “overdragen”, zonder een kopie te maken (dwz de inhoud wordt niet gedupliceerd, daarom kan het worden gebruikt op sommige niet-kopieerbare objecten, zoals een unique_ptr ). Het is ook mogelijk dat een object de inhoud van een tijdelijk object overneemt zonder een kopie te maken (en veel tijd bespaart), met std::move.

Deze link heeft me echt geholpen :

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

Het spijt me als mijn antwoord te laat komt, maar ik was ook op zoek naar een goede link voor de std::move, en ik vond de links hierboven een beetje “sober”.

Dit legt de nadruk op r-waardereferentie, in welke context je ze moet gebruiken, en ik denk dat het gedetailleerder is, daarom wilde ik deze link hier delen.


Antwoord 4, autoriteit 23%

V: Wat is std::move?

A: std::move()is een functie uit de C++ Standard Library voor het casten naar een rvalue-referentie.

Siplistisch std::move(t)is gelijk aan:

static_cast<T&&>(t);

Een r-waarde is een tijdelijke waarde die niet blijft bestaan ​​buiten de expressie die hem definieert, zoals een tussenresultaat van een functie dat nooit in een variabele wordt opgeslagen.

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

Een implementatie voor std::move() wordt gegeven in N2027: “Een korte introductie tot Rvalue-referenties”als volgt:

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

Zoals je kunt zien, retourneert std::moveT&&ongeacht of het wordt aangeroepen met een waarde (T), referentie type (T&), of rvalue-referentie (T&&).

V: Wat doet het?

A: Als cast doet het niets tijdens runtime. Het is alleen relevant tijdens het compileren om de compiler te vertellen dat je de referentie als een rvalue wilt blijven beschouwen.

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)`

Wat het nietdoet:

  • Maak een kopie van het argument
  • Bel de kopieerconstructor
  • Verander het argument-object

V: Wanneer moet het worden gebruikt?

A: Je moet std::movegebruiken als je functies wilt aanroepen die move-semantiek ondersteunen met een argument dat geen rvalue (tijdelijke expressie) is.

Dit roept bij mij de volgende vervolgvragen op:

  • Wat is move-semantiek? Verplaatssemantiek in tegenstelling tot kopieersemantiek is een programmeertechniek waarbij de leden van een object worden geïnitialiseerd door ‘over te nemen’ in plaats van de leden van een ander object te kopiëren. Zo’n ‘overname’ heeft alleen zin met pointers en resourcehandles, die goedkoop kunnen worden overgedragen door de pointer of integer-handle te kopiëren in plaats van de onderliggende gegevens.

  • Welke klassen en objecten ondersteunen verplaatsingssemantiek? Het is aan jou als ontwikkelaar om move-semantiek in je eigen klassen te implementeren als deze baat zouden hebben bij het overdragen van hun leden in plaats van ze te kopiëren. Zodra u move-semantiek heeft geïmplementeerd, profiteert u direct van het werk van veel bibliotheekprogrammeurs die ondersteuning hebben toegevoegd voor het efficiënt omgaan met klassen met move-semantiek.

  • Waarom kan de compiler het niet zelf bedenken? De compiler kan niet zomaar een andere overbelasting van een functie aanroepen, tenzij u het zegt. Je moet de compiler helpen kiezen of de reguliere of de verplaatste versie van de functie moet worden aangeroepen.

  • In welke situaties zou ik de compiler willen vertellen dat hij een variabele als een r-waarde moet behandelen? Dit zal hoogstwaarschijnlijk gebeuren in sjabloon- of bibliotheekfuncties, waarvan u weet dat een tussenresultaat zou kunnen worden gered.


Antwoord 5, autoriteit 10%

std::move zelf doet niet veel. Ik dacht dat het de verplaatste constructor voor een object noemde, maar het voert eigenlijk gewoon een typecast uit (een lvalue variabele casten naar een rvalue zodat de genoemde variabele als argument kan worden doorgegeven aan een move constructor of toewijzingsoperator).

Dus std::move wordt alleen gebruikt als een voorloper van het gebruik van move-semantiek. Verplaatssemantiek is in wezen een efficiënte manier om met tijdelijke objecten om te gaan.

Beschouw Object A = B + (C + (D + (E + F)));

Dit is een mooie code, maar E + F produceert een tijdelijk object. Dan produceert D + temp een ander tijdelijk object enzovoort. In elke normale “+”-operator van een klasse komen diepe kopieën voor.

Bijvoorbeeld

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

Het maken van het tijdelijke object in deze functie is nutteloos – deze tijdelijke objecten worden hoe dan ook aan het einde van de regel verwijderd als ze buiten het bereik vallen.

We kunnen liever de semantiek verplaatsen gebruiken om de tijdelijke objecten te “plunderen” en zoiets te doen als

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

Dit voorkomt dat er onnodig diepe kopieën worden gemaakt. Met verwijzing naar het voorbeeld, het enige deel waar diep kopiëren plaatsvindt, is nu E + F. De rest gebruikt verplaatsingssemantiek. De move constructor of toewijzingsoperator moet ook worden geïmplementeerd om het resultaat aan A toe te wijzen.


Antwoord 6, autoriteit 2%

“Wat is het?”en “Wat doet het?”is hierboven uitgelegd.

Ik zal een voorbeeld geven van “wanneer het moet worden gebruikt”.

We hebben bijvoorbeeld een klasse met veel bronnen, zoals een grote 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;
}

uitvoer zoals hieronder:

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

We kunnen zien dat std::movemet move constructorde transformatiebron gemakkelijk maakt.

Waar is std::movenog meer nuttig?

std::movekan ook handig zijn bij het sorteren van een reeks elementen. Veel sorteeralgoritmen (zoals selectiesortering en bellensortering) werken door het verwisselen van paren elementen. Voorheen moesten we onze toevlucht nemen tot kopieersemantiek om het om te wisselen. Nu kunnen we move-semantiek gebruiken, wat efficiënter is.

Het kan ook handig zijn als we de inhoud die door de ene slimme aanwijzer wordt beheerd, naar de andere willen verplaatsen.

Geciteerd:


Antwoord 7

std::movezelf doet niets liever dan een static_cast. Volgens cppreference.com

Het is precies gelijk aan een static_cast aan een rvalu-referentietype.

Het hangt dus af van het type variabele dat u toewijst na de move, of het type constructorsof assign operatorsheeft die een rvalue-parameter neemt, kan het wel of niet de inhoud van de originele variabele stelen, dus het kan de originele variabele in een unspecified statelaten:

Tenzij anders aangegeven, worden alle standaardbibliotheekobjecten die zijn verplaatst, in een geldige maar niet-gespecificeerde staat geplaatst.

Omdat er geen speciale move constructorof move assign operatoris voor ingebouwde letterlijke typen zoals gehele getallen en onbewerkte aanwijzers, is het dus slechts een eenvoudige kopie voor deze typen.


Antwoord 8

Hier is een volledig voorbeeld, waarbij std::move wordt gebruikt voor een (eenvoudige) aangepaste vector

Verwachte uitvoer:

c: [10][11]
 copy ctor called
 copy of c: [10][11]
 move ctor called
 moved c: [10][11]

Compileren als:

 g++ -std=c++2a -O2 -Wall -pedantic foo.cpp

Code:

#include <iostream>
#include <algorithm>
template<class T> class MyVector {
private:
    T *data;
    size_t maxlen;
    size_t currlen;
public:
    MyVector<T> () : data (nullptr), maxlen(0), currlen(0) { }
    MyVector<T> (int maxlen) : data (new T [maxlen]), maxlen(maxlen), currlen(0) { }
    MyVector<T> (const MyVector& o) {
        std::cout << "copy ctor called" << std::endl;
        data = new T [o.maxlen];
        maxlen = o.maxlen;
        currlen = o.currlen;
        std::copy(o.data, o.data + o.maxlen, data);
    }
    MyVector<T> (const MyVector<T>&& o) {
        std::cout << "move ctor called" << std::endl;
        data = o.data;
        maxlen = o.maxlen;
        currlen = o.currlen;
    }
    void push_back (const T& i) {
        if (currlen >= maxlen) {
            maxlen *= 2;
            auto newdata = new T [maxlen];
            std::copy(data, data + currlen, newdata);
            if (data) {
                delete[] data;
            }
            data = newdata;
        }
        data[currlen++] = i;
    }
    friend std::ostream& operator<<(std::ostream &os, const MyVector<T>& o) {
        auto s = o.data;
        auto e = o.data + o.currlen;;
        while (s < e) {
            os << "[" << *s << "]";
            s++;
        }
        return os;
    }
};
int main() {
    auto c = new MyVector<int>(1);
    c->push_back(10);
    c->push_back(11);
    std::cout << "c: " << *c << std::endl;
    auto d = *c;
    std::cout << "copy of c: " << d << std::endl;
    auto e = std::move(*c);
    delete c;
    std::cout << "moved c: " << e << std::endl;
}

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Other episodes