Vraag Wat zijn bewegingssemantiek?


Ik ben net klaar met luisteren naar de Software Engineering-radio podcastinterview met Scott Meyers met betrekking tot C ++ 0x. De meeste nieuwe functies waren logisch voor mij en ik ben nu echt enthousiast over C ++ 0x, met uitzondering van één. Ik snap het nog steeds niet verplaats de semantiek... Wat zijn het precies?


1374
2018-06-23 22:46


oorsprong


antwoorden:


Ik vind het het gemakkelijkst om bewegingssemantiek met voorbeeldcode te begrijpen. Laten we beginnen met een heel eenvoudige stringklasse die alleen een aanwijzer bevat naar een heap toegewezen blok geheugen:

#include <cstring>
#include <algorithm>

class string
{
    char* data;

public:

    string(const char* p)
    {
        size_t size = strlen(p) + 1;
        data = new char[size];
        memcpy(data, p, size);
    }

Omdat we ervoor hebben gekozen om de herinnering zelf te beheren, moeten we de regel van drie. Ik ga het schrijven van de toewijzingsoperator uitstellen en alleen de destructor en de copy constructor nu implementeren:

    ~string()
    {
        delete[] data;
    }

    string(const string& that)
    {
        size_t size = strlen(that.data) + 1;
        data = new char[size];
        memcpy(data, that.data, size);
    }

De copy-constructor definieert wat het betekent om string-objecten te kopiëren. De parameter const string& that bindt aan alle expressies van het type string waarmee je kopieën kunt maken in de volgende voorbeelden:

string a(x);                                    // Line 1
string b(x + y);                                // Line 2
string c(some_function_returning_a_string());   // Line 3

Nu komt het belangrijkste inzicht in de bewegingssemantiek. Merk op dat alleen in de eerste regel we kopiëren x is dit diepe exemplaar echt nodig, omdat we het misschien willen inspecteren x later en zou zeer verrast zijn als x was op de een of andere manier veranderd. Heb je gezien hoe ik net zei x drie keer (vier keer als je deze zin opneemt) en betekende het exact hetzelfde object elke keer? We noemen expressies zoals x "Lvalues".

De argumenten in regel 2 en 3 zijn geen lwaarden, maar rwaarden, omdat de onderliggende tekenreeksobjecten geen namen hebben, zodat de klant ze later op geen enkele manier opnieuw kan inspecteren. rwaarden duiden tijdelijke objecten aan die bij de volgende puntkomma worden vernietigd (om preciezer te zijn: aan het einde van de volledige uitdrukking die lexicaal de rvalue bevat). Dit is belangrijk omdat tijdens de initialisatie van b en c, we konden doen wat we wilden met de bron-string, en de klant kon geen verschil zien!

C ++ 0x introduceert een nieuw mechanisme genaamd "rvalue reference" dat, onder andere, stelt ons in staat om rvalue-argumenten te detecteren via functieoverbelasting. Alles wat we moeten doen is een constructor schrijven met een rvalue-referentieparameter. Binnen die constructor kunnen we doen alles wat we willen met de bron, zolang we het erin laten sommige geldige staat:

    string(string&& that)   // string&& is an rvalue reference to a string
    {
        data = that.data;
        that.data = nullptr;
    }

Wat hebben we hier gedaan? In plaats van de heapgegevens diep te kopiëren, hebben we zojuist de aanwijzer gekopieerd en vervolgens de originele pointer op nul gezet. In feite hebben we de gegevens "gestolen" die oorspronkelijk tot de bronreeks behoorden. Nogmaals, het belangrijkste inzicht is dat de klant onder geen beding zou kunnen vaststellen dat de bron was gewijzigd. Aangezien we hier niet echt een kopie maken, noemen we deze constructor een "verhuizersconstructeur". Het is zijn taak om middelen van het ene object naar het andere te verplaatsen in plaats van ze te kopiëren.

Gefeliciteerd, je begrijpt nu de basisprincipes van bewegingssemantiek! Laten we doorgaan met het implementeren van de toewijzingsoperator. Als u niet bekend bent met de idioom kopiëren en omwisselen, leer het en kom terug, want het is een geweldig C ++ -idioom met betrekking tot uitzonderingsveiligheid.

    string& operator=(string that)
    {
        std::swap(data, that.data);
        return *this;
    }
};

Huh, dat is het? "Waar is de Rvalue-referentie?" je zou kunnen vragen. "We hebben het hier niet nodig!" is mijn antwoord :)

Merk op dat we de parameter doorgeven that  op waarde, dus that moet worden geïnitialiseerd net als elk ander stringobject. Precies hoe is het that wordt geïnitialiseerd? In de oude dagen van C ++ 98, het antwoord zou zijn "door de copy constructor". In C ++ 0x kiest de compiler tussen de kopie-constructor en de verplaatsingsconstructor op basis van het feit of het argument voor de toewijzingsoperator een lvalue of een rvalue is.

Dus als je zegt a = b, de kopieer constructor zal initialiseren that (omdat de uitdrukking b is een lvalue) en de opdrachtbeheerder wisselt de inhoud om met een nieuw gemaakte, diepe kopie. Dat is de definitie van het idioom kopiëren en verwisselen - maak een kopie, wissel de inhoud met de kopie en verwijder de kopie door het bereik te verlaten. Niets nieuws hier.

Maar als je zegt a = x + y, de verplaats de constructeur zal initialiseren that (omdat de uitdrukking x + y is een rvalue), dus er is geen diepgaande kopie bij betrokken, alleen een efficiënte zet. that is nog steeds een onafhankelijk object van het argument, maar de constructie ervan was triviaal, omdat de heap-gegevens niet gekopieerd hoefden te worden, gewoon verplaatst. Het was niet nodig om het te kopiëren omdat x + y is een rvalue, en nogmaals, het is goed om te verplaatsen van tekenreeksobjecten die worden aangeduid met rwaarden.

Samenvattend maakt de copy-constructor een deep-copy, omdat de bron onaangeroerd moet blijven. De verplaatsingsconstructor daarentegen kan alleen de aanwijzer kopiëren en vervolgens de aanwijzer in de bron op nul zetten. Het is prima om het bronobject op deze manier te "nietig verklaren", omdat de cliënt het object niet opnieuw kan inspecteren.

Ik hoop dat dit voorbeeld het belangrijkste punt heeft overgenomen. Er is veel meer aan rvalue-verwijzingen en verplaats semantiek die ik bewust heb weggelaten om het simpel te houden. Raadpleeg alsjeblieft als je meer details wilt mijn aanvullend antwoord.


2034
2018-06-24 12:40



Mijn eerste antwoord was een uiterst vereenvoudigde inleiding tot het verplaatsen van semantiek en veel details werden expres weggelaten om het eenvoudig te houden. Er is echter nog veel meer om de semantiek te verplaatsen en ik dacht dat het tijd was voor een tweede antwoord om de lacunes op te vullen. Het eerste antwoord is al vrij oud en het voelde niet goed om het simpelweg te vervangen door een geheel andere tekst. Ik denk dat het nog steeds goed dient als een eerste introductie. Maar als je dieper wilt graven, lees dan verder :)

Stephan T. Lavavej nam de tijd om waardevolle feedback te geven. Heel erg bedankt, Stephan!

Invoering

Met semantiek verplaatsen kan een object onder bepaalde voorwaarden eigenaar worden van de externe bronnen van een ander object. Dit is op twee manieren belangrijk:

  1. Het veranderen van dure kopieën in goedkope zetten. Zie mijn eerste antwoord voor een voorbeeld. Houd er rekening mee dat als een object ten minste één externe bron niet (of direct of indirect via de bijbehorende lidobjecten) beheert, verplaatsing van semantiek geen voordelen biedt ten opzichte van kopie-semantiek. In dat geval betekent het kopiëren van een object en het verplaatsen van een object precies hetzelfde:

    class cannot_benefit_from_move_semantics
    {
        int a;        // moving an int means copying an int
        float b;      // moving a float means copying a float
        double c;     // moving a double means copying a double
        char d[64];   // moving a char array means copying a char array
    
        // ...
    };
    
  2. Implementeren van veilige "alleen-verplaatsen" types; dat wil zeggen, typen waarvoor kopiëren niet logisch is, maar verplaatsen wel. Voorbeelden hiervan zijn vergrendelingen, bestandshandvatten en slimme aanwijzers met unieke eigendomssemantiek. Opmerking: dit antwoord bespreekt std::auto_ptr, een verouderd standaard C ++ 98 bibliotheeksjabloon, dat werd vervangen door std::unique_ptr in C ++ 11. Tussenliggende C ++ -programmeurs zijn waarschijnlijk op zijn minst enigszins bekend met std::auto_ptr, en vanwege de "bewegingssemantiek" die het weergeeft, lijkt het een goed startpunt voor het bespreken van bewegingssemantiek in C ++ 11. YMMV.

Wat is een zet?

De standaardbibliotheek C ++ 98 biedt een slimme aanwijzer met de naam unieke unieke semantiek std::auto_ptr<T>. In het geval u onbekend bent met auto_ptr, het doel is om te garanderen dat een dynamisch toegewezen object altijd wordt vrijgegeven, zelfs in het licht van uitzonderingen:

{
    std::auto_ptr<Shape> a(new Triangle);
    // ...
    // arbitrary code, could throw exceptions
    // ...
}   // <--- when a goes out of scope, the triangle is deleted automatically

Het ongewone ding over auto_ptr is het "kopieer" -gedrag:

auto_ptr<Shape> a(new Triangle);

      +---------------+
      | triangle data |
      +---------------+
        ^
        |
        |
        |
  +-----|---+
  |   +-|-+ |
a | p | | | |
  |   +---+ |
  +---------+

auto_ptr<Shape> b(a);

      +---------------+
      | triangle data |
      +---------------+
        ^
        |
        +----------------------+
                               |
  +---------+            +-----|---+
  |   +---+ |            |   +-|-+ |
a | p |   | |          b | p | | | |
  |   +---+ |            |   +---+ |
  +---------+            +---------+

Merk op hoe de initialisatie van b met a doet niet kopieer de driehoek, maar in plaats daarvan wordt het eigendom van de driehoek overgedragen a naar b. We zeggen ook "a is verhuisd naar  b"of" de driehoek is verhuisd van a  naar  bDit klinkt misschien verwarrend, omdat de driehoek zelf altijd op dezelfde plek in het geheugen blijft.

Een object verplaatsen betekent dat het eigendom van een bron die het beheert, wordt overgedragen naar een ander object.

De kopie-constructor van auto_ptr ziet er waarschijnlijk ongeveer zo uit (enigszins vereenvoudigd):

auto_ptr(auto_ptr& source)   // note the missing const
{
    p = source.p;
    source.p = 0;   // now the source no longer owns the object
}

Gevaarlijke en ongevaarlijke bewegingen

Het gevaarlijke ding over auto_ptr is dat wat syntactisch lijkt op een kopie eigenlijk een zet is. Probeert een ledenfunctie te bellen op een verplaatst-van auto_ptr zal ongedefinieerd gedrag oproepen, dus je moet heel voorzichtig zijn om geen een te gebruiken auto_ptr nadat het is verplaatst van:

auto_ptr<Shape> a(new Triangle);   // create triangle
auto_ptr<Shape> b(a);              // move a into b
double area = a->area();           // undefined behavior

Maar auto_ptr is niet altijd gevaarlijk. Fabrieksfuncties zijn een prima gebruiksvoorbeeld voor auto_ptr:

auto_ptr<Shape> make_triangle()
{
    return auto_ptr<Shape>(new Triangle);
}

auto_ptr<Shape> c(make_triangle());      // move temporary into c
double area = make_triangle()->area();   // perfectly safe

Merk op hoe beide voorbeelden hetzelfde syntactische patroon volgen:

auto_ptr<Shape> variable(expression);
double area = expression->area();

En toch roept een van hen ongedefinieerd gedrag op, terwijl de andere dat niet. Dus wat is het verschil tussen de uitdrukkingen a en make_triangle()? Zijn ze niet allebei van hetzelfde type? Dat zijn ze inderdaad, maar ze zijn anders waardecategorieën.

Waardecategorieën

Vanzelfsprekend moet er een diepgaand verschil zijn tussen de uitdrukking a wat staat voor een auto_ptr variabele en de expressie make_triangle() die de roep van een functie aanduidt die een auto_ptr naar waarde, waardoor een nieuwe tijdelijke situatie wordt gecreëerd auto_ptr object elke keer dat het wordt aangeroepen. a is een voorbeeld van een lvalue, terwijl make_triangle() is een voorbeeld van een rvalue.

Verhuizen van lvalues ​​zoals a is gevaarlijk, omdat we later zouden kunnen proberen om een ​​ledenfunctie via te bellen a, waarbij ongedefinieerd gedrag wordt aangeroepen. Aan de andere kant, verplaatsen van rvalues ​​zoals make_triangle() is volkomen veilig, want nadat de constructeur van de kopie zijn werk heeft gedaan, kunnen we de tijdelijke oplossing niet opnieuw gebruiken. Er is geen uitdrukking die tijdelijk betekent; als we gewoon schrijven make_triangle()nogmaals, we krijgen een verschillend tijdelijk. In feite is het tijdelijke verplaatst-al op de volgende regel verdwenen:

auto_ptr<Shape> c(make_triangle());
                                  ^ the moved-from temporary dies right here

Merk op dat de letters l en r een historische oorsprong hebben aan de linker- en rechterkant van een opdracht. Dit is niet langer het geval in C ++, omdat er lwaarden zijn die niet aan de linkerkant van een toewijzing kunnen verschijnen (zoals arrays of door de gebruiker gedefinieerde typen zonder een toewijzingsoperator), en er zijn rwaarden die dat kunnen (alle waarden van klassenoorten met een opdrachtoperator).

Een rvalue van het klassentype is een uitdrukking waarvan de evaluatie een tijdelijk object maakt.   Onder normale omstandigheden geeft geen andere uitdrukking binnen dezelfde scope hetzelfde tijdelijke object aan.

Rvalue-referenties

We begrijpen nu dat verhuizen van lvalues ​​potentieel gevaarlijk is, maar het verplaatsen van rvalues ​​is onschadelijk. Als C ++ taalondersteuning had om lvalue-argumenten te onderscheiden van rvalue-argumenten, konden we het verplaatsen van lwaarden volledig verbieden, of op zijn minst overstappen van lvalues uitdrukkelijk op oproepsite, zodat we niet langer per ongeluk bewegen.

C ++ 11's antwoord op dit probleem is rvalue-referenties. Een rvalue-verwijzing is een nieuw soort referentie dat alleen aan rvalues ​​bindt, en de syntaxis is dat X&&. De goede oude referentie X& is nu bekend als een lvalue referentie. (Let daar op X&& is niet een verwijzing naar een referentie; er bestaat niet zoiets in C ++.)

Als we gooien const in de mix hebben we al vier verschillende soorten referenties. Welke soorten uitdrukkingen van het type X kunnen ze binden aan?

            lvalue   const lvalue   rvalue   const rvalue
---------------------------------------------------------              
X&          yes
const X&    yes      yes            yes      yes
X&&                                 yes
const X&&                           yes      yes

In de praktijk kun je het vergeten const X&&. Beperkt zijn om te lezen van rwaarden is niet erg nuttig.

Een rvalue-referentie X&& is een nieuw soort referentie dat alleen aan rvalues ​​bindt.

Impliciete conversies

Rvalue-referenties hebben verschillende versies doorlopen. Sinds versie 2.1, een rvalue-referentie X&& bindt ook aan alle waardecategorieën van een ander type Y, op voorwaarde dat er een impliciete conversie van is Y naar X. In dat geval een tijdelijke van het type X is gemaakt en de verwijzing naar de Rvalue is gekoppeld aan die tijdelijke:

void some_function(std::string&& r);

some_function("hello world");

In het bovenstaande voorbeeld "hello world" is een lvalue van het type const char[12]. Aangezien er een impliciete conversie van is const char[12] door const char* naar std::string, een tijdelijk type std::string is gemaakt, en r is gebonden aan dat tijdelijk. Dit is een van de gevallen waarin het onderscheid tussen rvalues ​​(expressies) en temporaries (objecten) enigszins wazig is.

Verplaats constructeurs

Een handig voorbeeld van een functie met een X&& parameter is de verplaats de constructeur  X::X(X&& source). Het doel is om het eigendom van de beheerde bron over te dragen van de bron naar het huidige object.

In C ++ 11, std::auto_ptr<T> is vervangen door std::unique_ptr<T> die gebruikmaakt van rvalue-referenties. Ik zal een vereenvoudigde versie van ontwikkelen en bespreken unique_ptr. Eerst kapselen we een onbewerkte aanwijzer in en overbelasten we de operatoren -> en *, dus onze klas voelt als een aanwijzer:

template<typename T>
class unique_ptr
{
    T* ptr;

public:

    T* operator->() const
    {
        return ptr;
    }

    T& operator*() const
    {
        return *ptr;
    }

De constructor neemt het object in eigendom en de destructor verwijdert het:

    explicit unique_ptr(T* p = nullptr)
    {
        ptr = p;
    }

    ~unique_ptr()
    {
        delete ptr;
    }

Nu komt het interessante deel, de verhuisconstructeur:

    unique_ptr(unique_ptr&& source)   // note the rvalue reference
    {
        ptr = source.ptr;
        source.ptr = nullptr;
    }

Deze move-constructor doet precies wat de auto_ptr copy constructor did, maar het kan alleen worden geleverd met rvalues:

unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a);                 // error
unique_ptr<Shape> c(make_triangle());   // okay

De tweede regel kan niet worden gecompileerd, omdat a is een lvalue, maar de parameter unique_ptr&& source kan alleen worden gebonden aan rwaarden. Dit is precies wat we wilden; gevaarlijke bewegingen mogen nooit impliciet zijn. De derde regel compileert prima, want make_triangle() is een rwaarde. De verplaatsingsconstructeur draagt ​​het eigendom over van de tijdelijke naar c. Nogmaals, dit is precies wat we wilden.

De verplaatsingsconstructeur draagt ​​het eigendom van een beheerde resource over naar het huidige object.

Verplaats toewijzingsoperators

Het laatste ontbrekende stuk is de operator voor verplaatsingstoewijzing. Het is zijn taak om de oude bron vrij te geven en de nieuwe bron uit zijn argument te halen:

    unique_ptr& operator=(unique_ptr&& source)   // note the rvalue reference
    {
        if (this != &source)    // beware of self-assignment
        {
            delete ptr;         // release the old resource

            ptr = source.ptr;   // acquire the new resource
            source.ptr = nullptr;
        }
        return *this;
    }
};

Merk op hoe deze implementatie van de operator voor verplaatsingstoewijzing de logica dupliceert van zowel de destructor als de verplaatsingsconstructor. Kent u het copy-and-swap-idioom? Het kan ook worden toegepast om de semantiek te verplaatsen als het verplaats-en-wissel-idioom:

    unique_ptr& operator=(unique_ptr source)   // note the missing reference
    {
        std::swap(ptr, source.ptr);
        return *this;
    }
};

Dat source is een variabele van het type unique_ptr, het wordt geïnitialiseerd door de verhuizer; dat wil zeggen dat het argument in de parameter wordt verplaatst. Het argument moet nog steeds een rvalue zijn, omdat de verplaatsingsconstructor zelf een rvalue-referentieparameter heeft. Wanneer de stuurstroom de sluitbeugel van bereikt operator=, source valt buiten het bereik, waardoor de oude bron automatisch wordt vrijgegeven.

De operator voor verplaatsingstoewijzing draagt ​​het eigendom van een beheerde resource over naar het huidige object en geeft de oude resource vrij.   Het verplaats-en-verwissel idioom vereenvoudigt de implementatie.

Verhuizen van lvalues

Soms willen we van lvalues ​​afwijken. Dat wil zeggen dat we soms willen dat de compiler een lvalue behandelt alsof het een rvalue is, dus het kan de constructor van de verplaatsing aanroepen, hoewel het potentieel onveilig zou kunnen zijn. Voor dit doel biedt C ++ 11 een standaard bibliotheekfunctiesjabloon genaamd std::move in de koptekst <utility>. Deze naam is een beetje ongelukkig, omdat std::move voegt eenvoudig een waarde toe aan een waarde; het doet niet verplaats alles alleen. Het alleen maar stelt in beweging. Misschien had het een naam moeten hebben std::cast_to_rvalue of std::enable_move, maar we zitten nu vast aan de naam.

Hier is hoe je expliciet van een lvalue verandert:

unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a);              // still an error
unique_ptr<Shape> c(std::move(a));   // okay

Merk op dat na de derde regel, a bezit niet langer een driehoek. Dat is goed, omdat door uitdrukkelijk schrift std::move(a), we maakten onze intenties duidelijk: "Beste constructeur, doe waar je zin in hebt a om te initialiseren c; Ik geef niet om a meer. Voel je vrij om je gang te gaan a."

std::move(some_lvalue) werpt een lvalue naar een rvalue, waardoor een volgende zet mogelijk is.

Xvalues

Merk op dat hoewel std::move(a) is een rvalue, de evaluatie doet dat niet maak een tijdelijk object. Dit raadsel dwong de commissie om een ​​derde waardecategorie in te voeren. Iets dat kan worden gebonden aan een rvalue-verwijzing, ook al is het geen rvalue in de traditionele zin, wordt een xWaarde (eXpiring-waarde). De traditionele rwaarde werd hernoemd naar prvalues (Pure rwaarden).

Beide prwaarden en x-waarden zijn rwaarden. Xwaarden en lwaarden zijn beide glvalues (Gegeneraliseerde lwaarden). De relaties zijn gemakkelijker te begrijpen met een diagram:

        expressions
          /     \
         /       \
        /         \
    glvalues   rvalues
      /  \       /  \
     /    \     /    \
    /      \   /      \
lvalues   xvalues   prvalues

Merk op dat alleen x-waarden echt nieuw zijn; de rest is gewoon te wijten aan hernoemen en groeperen.

C ++ 98 rwaarden zijn in C ++ 11 de prvalues ​​genoemd. Vervang mentaal "rvalue" in de voorgaande paragrafen door "prvalue".

Functies verlaten

Tot nu toe hebben we beweging gezien in lokale variabelen en in functieparameters. Maar bewegen is ook mogelijk in de tegenovergestelde richting. Als een functie per waarde terugkeert, wordt een object op de oproepsite (waarschijnlijk een lokale variabele of een tijdelijk, maar kan elk soort object zijn) geïnitialiseerd met de expressie na de return verklaring als een argument voor de verhuizersconstructeur:

unique_ptr<Shape> make_triangle()
{
    return unique_ptr<Shape>(new Triangle);
}          \-----------------------------/
                  |
                  | temporary is moved into c
                  |
                  v
unique_ptr<Shape> c(make_triangle());

Misschien verrassend, automatische objecten (lokale variabelen die niet als gedeclareerd zijn static) kan ook zijn stilzwijgend verplaatst van functies:

unique_ptr<Shape> make_square()
{
    unique_ptr<Shape> result(new Square);
    return result;   // note the missing std::move
}

Hoe komt het dat de verhuisconstructeur de lvalue accepteert result als een argument? De reikwijdte van result staat op het punt te eindigen en zal tijdens het afwikkelen van de stapel worden vernietigd. Niemand kan achteraf dat klagen result op de een of andere manier was veranderd; wanneer de stuurstroom terug is bij de beller, result bestaat niet meer! Om die reden heeft C ++ 11 een speciale regel die het automatisch retourneren van objecten uit functies mogelijk maakt zonder te hoeven schrijven std::move. Eigenlijk zou je dat moeten doen nooit gebruik std::move om automatische objecten uit functies te verwijderen, omdat dit de "benoemde rendementsoptimalisatie" (NRVO) verhindert.

Nooit gebruiken std::move om automatische objecten uit functies te verwijderen.

Merk op dat in beide fabrieksfuncties het retourneertype een waarde is, geen rvalue-referentie. Rvalue-verwijzingen zijn nog steeds verwijzingen en zoals altijd moet u nooit een verwijzing naar een automatisch object retourneren; de beller zou eindigen met een bungelende referentie als je de compiler hebt misleid in het accepteren van je code, zoals deze:

unique_ptr<Shape>&& flawed_attempt()   // DO NOT DO THIS!
{
    unique_ptr<Shape> very_bad_idea(new Square);
    return std::move(very_bad_idea);   // WRONG!
}

Nooit automatische objecten retourneren door middel van de referentie van de Rvalue. Verhuizen wordt uitsluitend uitgevoerd door de verhuisconstructeur, niet door std::move, en niet alleen door een rvalue aan een rvalue-referentie te binden.

Verhuizen naar leden

Vroeg of laat ga je code als volgt schrijven:

class Foo
{
    unique_ptr<Shape> member;

public:

    Foo(unique_ptr<Shape>&& parameter)
    : member(parameter)   // error
    {}
};

Kortom, de compiler klaagt dat parameter is een lvalue. Als u naar het type kijkt, ziet u een rvalue-verwijzing, maar een rvalue-verwijzing betekent eenvoudigweg "een verwijzing die is gebonden aan een rvalue"; het doet niet betekent dat de referentie zelf een rvalue is! Inderdaad, parameter is gewoon een gewone variabele met een naam. Je kunt gebruiken parameter zo vaak als je wilt in het lichaam van de constructor, en het duidt altijd hetzelfde object aan. Het impliciet ervan afwijken is gevaarlijk, vandaar dat de taal het verbiedt.

Een benoemde rwaarde-referentie is een lvalue, net als elke andere variabele.

De oplossing is om de verplaatsing handmatig in te schakelen:

class Foo
{
    unique_ptr<Shape> member;

public:

    Foo(unique_ptr<Shape>&& parameter)
    : member(std::move(parameter))   // note the std::move
    {}
};

Dat zou je kunnen betogen parameter wordt niet meer gebruikt na de initialisatie van member. Waarom is er geen speciale regel om stil in te voegen std::move net als met retourwaarden? Waarschijnlijk omdat het te veel last zou zijn voor de compiler-implementors. Wat als bijvoorbeeld het constructeur-orgaan in een andere vertaaleenheid zat? Daarentegen moet de regel voor de retourwaarde eenvoudigweg de symbooltabellen controleren om te bepalen of de ID na de return trefwoord geeft een automatisch object aan.

Je kunt ook passen parameter op waarde. Voor alleen-typen zoals unique_ptr, het lijkt erop dat er nog geen vaststaand idioom bestaat. Persoonlijk geef ik de voorkeur aan pass-by-waarde, omdat dit minder rommel in de interface veroorzaakt.

Speciale ledenfuncties

C ++ 98 verklaart impliciet drie speciale lidfuncties op aanvraag, dat wil zeggen, wanneer ze ergens nodig zijn: de kopie-constructor, de kopie-toewijzingsoperator en de destructor.

X::X(const X&);              // copy constructor
X& X::operator=(const X&);   // copy assignment operator
X::~X();                     // destructor

Rvalue-referenties hebben verschillende versies doorlopen. Sinds versie 3.0 declareert C ++ 11 twee extra speciale lidfuncties op aanvraag: de verplaatsingsconstructor en de verplaatsingstoewijzing. Merk op dat VC10 en VC11 nog niet voldoen aan versie 3.0, dus u zult ze zelf moeten implementeren.

X::X(X&&);                   // move constructor
X& X::operator=(X&&);        // move assignment operator

Deze twee nieuwe speciale lidfuncties worden alleen impliciet gedeclareerd als geen van de speciale lidfuncties handmatig worden gedeclareerd. Als u uw eigen verplaatsingsconstructor of verplaatsingsopdrachtoperator declareert, worden noch de constructeur van de kopie, noch de operator voor de toewijzing van de kopie impliciet aangegeven.

Wat betekenen deze regels in de praktijk?

Als u een klasse schrijft zonder onbeheerde bronnen, hoeft u niet zelf een van de vijf speciale lidfuncties te declareren, en krijgt u de juiste kopie-semantiek en zet u gratis de semantiek over. Anders moet u de speciale lidfuncties zelf implementeren. Natuurlijk, als uw klas niet profiteert van bewegingssemantiek, is het niet nodig om de speciale verplaatsingsoperaties te implementeren.

Merk op dat de operator voor de toewijzing van de kopie en de operator voor verplaatsingstoewijzing kunnen worden samengevoegd tot een enkele, geünificeerde toewijzingsoperator, waarbij het argument als volgt wordt overgenomen:

X& X::operator=(X source)    // unified assignment operator
{
    swap(source);            // see my first answer for an explanation
    return *this;
}

Op deze manier daalt het aantal speciale lidfuncties dat moet worden geïmplementeerd van vijf naar vier. Er is een afweging tussen uitzonderingsveiligheid en efficiëntie hier, maar ik ben geen expert op dit gebied.

Referenties doorsturen (eerder bekend als Universele referenties)

Overweeg de volgende functiesjabloon:

template<typename T>
void foo(T&&);

Je zou verwachten T&& om alleen aan rvalues ​​te binden, omdat het op het eerste gezicht lijkt op een rvalue-verwijzing. Maar het blijkt, T&& bindt ook aan lvalues:

foo(make_triangle());   // T is unique_ptr<Shape>, T&& is unique_ptr<Shape>&&
unique_ptr<Shape> a(new Triangle);
foo(a);                 // T is unique_ptr<Shape>&, T&& is unique_ptr<Shape>&

Als het argument een rvalue van het type is X, T wordt afgeleid X, Vandaar T&& middelen X&&. Dit is wat iedereen zou verwachten. Maar als het argument een lvalue van het type is X, vanwege een speciale regel, T wordt afgeleid X&, Vandaar T&& zou zoiets betekenen X& &&. Maar omdat C ++ nog steeds geen idee heeft van verwijzingen naar referenties, is het type X& && is ingestort in X&. Dit klinkt misschien in het begin verwarrend en nutteloos, maar referentie-instorting is essentieel voor perfecte doorsturing (die hier niet worden besproken).

T && is geen rvalue-verwijzing, maar een doorstuurreferentie. Het bindt zich ook aan lvalues, in welk geval T en T&& zijn beide lvalue-verwijzingen.

Als u een functiesjabloon wilt beperken tot rwaarden, kunt u combineren SFINAE met typekenmerken:

#include <type_traits>

template<typename T>
typename std::enable_if<std::is_rvalue_reference<T&&>::value, void>::type
foo(T&&);

Implementatie van verplaatsen

Nu u begrijpt dat verwijzing mislukt, kunt u dit als volgt doen std::move is geïmplementeerd:

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

Zoals je kan zien, move accepteert elke soort parameter dankzij de doorstuurreferentie T&&en retourneert een rvalue-verwijzing. De std::remove_reference<T>::type oproep met een meta-functie is noodzakelijk omdat anders voor lwaarden van het type X, het retourtype zou zijn X& &&, die zou instorten X&. Sinds t is altijd een lvalue (onthoud dat een benoemde rvalue-verwijzing een lvalue is), maar we willen binden t naar een rvalue-verwijzing, moeten we expliciet casten t naar het juiste retourtype. De aanroep van een functie die een rvalue-verwijzing retourneert, is zelf een x-waarde. Nu weet je waar xwaarden vandaan komen;)

De aanroep van een functie die een rvalue-verwijzing retourneert, zoals std::move, is een xwaarde.

Houd er rekening mee dat retourneren met rwaarde-verwijzing in dit voorbeeld prima is, omdat t duidt niet een automatisch object aan, maar een object dat is doorgegeven door de beller.


891
2017-07-18 11:24



Verplaatsingssemantiek is gebaseerd op rvalue-referenties.
Een rvalue is een tijdelijk object dat aan het einde van de expressie wordt vernietigd. In huidige C ++ binden rwaarden alleen aan const gevonden. C ++ 1x staat niet-const rvalue-referenties, gespeld T&&, die verwijzen naar een rvalue-object.
Aangezien een rvalue aan het einde van een expressie gaat sterven, kan dat de gegevens stelen. In plaats van kopiëren het in een ander object, jij verhuizing zijn gegevens erin.

class X {
public: 
  X(X&& rhs) // ctor taking an rvalue reference, so-called move-ctor
    : data_()
  {
     // since 'x' is an rvalue object, we can steal its data
     this->swap(std::move(rhs));
     // this will leave rhs with the empty data
  }
  void swap(X&& rhs);
  // ... 
};

// ...

X f();

X x = f(); // f() returns result as rvalue, so this calls move-ctor

In de bovenstaande code, met oude compilers het resultaat van f() is gekopieerd in x gebruik makend van X's copy constructor. Als uw compiler bewegingssemantiek en ondersteunt X heeft een move-constructor, dan wordt dat in plaats daarvan genoemd. Sinds het rhs argument is een rvalue, we weten dat het niet langer nodig is en we kunnen de waarde ervan stelen.
Dus de waarde is verhuisd van de naamloze tijdelijke keerde terug van f() naar x (terwijl de gegevens van x, geïnitialiseerd naar een leeg X, wordt verplaatst naar het tijdelijke, dat na de opdracht wordt vernietigd).


67
2018-06-23 23:12



Stel dat je een functie hebt die een substantieel object retourneert:

Matrix multiply(const Matrix &a, const Matrix &b);

Wanneer je code als volgt schrijft:

Matrix r = multiply(a, b);

dan zal een gewone C ++ - compiler een tijdelijk object maken voor het resultaat van multiply(), bel de constructeur van de kopie om te initialiseren ren vernietig vervolgens de tijdelijke retourwaarde. Verplaats de semantiek in C ++ 0x laat de "verplaatsingsconstructor" worden opgeroepen om te initialiseren rdoor de inhoud ervan te kopiëren en de tijdelijke waarde weg te gooien zonder deze te vernietigen.

Dit is vooral belangrijk als (zoals misschien de Matrix voorbeeld hierboven), wijst het te kopiëren object extra geheugen toe aan de heap om zijn interne representatie op te slaan. Een copy-constructor zou ofwel een volledige kopie van de interne representatie moeten maken, of intermitterende referentie- en copy-on-write-semantiek gebruiken. Een verhuizersconstructeur zou alleen het heap-geheugen verlaten en de aanwijzer gewoon in de kopie kopiëren Matrix voorwerp.


46
2018-06-23 22:53



Als je echt geïnteresseerd bent in een goede, diepgaande uitleg over bewegingssemantiek, raad ik je aan het originele document te lezen, "Een voorstel om Move Semantics-ondersteuning toe te voegen aan de C ++ -taal." 

Het is heel toegankelijk en gemakkelijk te lezen en het is een uitstekende case voor de voordelen die het biedt. Er zijn andere meer recente en bijgewerkte artikelen over verplaatsingssemantiek beschikbaar de WG21-website, maar deze is waarschijnlijk het meest recht toe recht aan, omdat het dingen vanuit een bovenliggend niveau benadert en niet erg in de details van de korrelige taal komt.


27
2018-06-23 23:32



Verplaats semantiek gaat over middelen overdragen in plaats van ze te kopiëren wanneer niemand de bronwaarde meer nodig heeft.

In C ++ 03 worden objecten vaak gekopieerd, maar worden ze vernietigd of toegewezen voordat een code de waarde opnieuw gebruikt. Wanneer u bijvoorbeeld terugkeert op waarde van een functie, tenzij RVO begint, wordt de waarde die u retourneert gekopieerd naar het stackframe van de beller en valt deze vervolgens buiten bereik en wordt deze vernietigd. Dit is slechts een van de vele voorbeelden: zie pass-by-value wanneer het bronobject tijdelijke, algoritmen zoals is sort die items gewoon herschikken, opnieuw toewijzen in vector wanneer het capacity() wordt overschreden, etc.

Wanneer dergelijke kopie / vernietigingsparen duur zijn, is dat meestal omdat het object een zwaargewicht middel bezit. Bijvoorbeeld, vector<string> kan een dynamisch toegewezen geheugenblok bezitten dat een reeks van bevat string objecten, elk met een eigen dynamisch geheugen. Het kopiëren van een dergelijk object is kostbaar: u moet nieuw geheugen toewijzen voor elke dynamisch toegewezen blokken in de bron en alle waarden kopiëren. Dan je moet al dat geheugen dat je net hebt gekopieerd vrijgeven. Echter, in beweging een grote vector<string> betekent gewoon een paar aanwijzers kopiëren (die verwijzen naar het dynamische geheugenblok) naar de bestemming en ze op nul zetten in de bron.


21
2018-04-08 19:47



In eenvoudige (praktische) voorwaarden:

Een object kopiëren betekent het kopiëren van de "statische" leden en het aanroepen van de new operator voor zijn dynamische objecten. Rechts?

class A
{
   int i, *p;

public:
   A(const A& a) : i(a.i), p(new int(*a.p)) {}
   ~A() { delete p; }
};

Echter, om verhuizing een object (ik herhaal, in een praktisch oogpunt) houdt alleen in om de wijzers van dynamische objecten te kopiëren en niet om nieuwe aan te maken.

Maar is dat niet gevaarlijk? Natuurlijk kunt u een dynamisch object twee keer vernietigen (segmentatiefout). Dus om dit te voorkomen, moet u de bronaanwijzingen "ongeldig maken" om te voorkomen dat ze twee keer worden vernietigd:

class A
{
   int i, *p;

public:
   // Movement of an object inside a copy constructor.
   A(const A& a) : i(a.i), p(a.p)
   {
     a.p = nullptr; // pointer invalidated.
   }

   ~A() { delete p; }
   // Deleting NULL, 0 or nullptr (address 0x0) is safe. 
};

Ok, maar als ik een object verplaats, wordt het bronobject nutteloos, niet? Natuurlijk, maar in bepaalde situaties is dat erg handig. De meest voor de hand liggende is wanneer ik een functie met een anoniem object oproept (tijdelijk, rvalue-object, ..., je kunt het met verschillende namen noemen):

void heavyFunction(HeavyType());

In die situatie wordt een anoniem object gemaakt, vervolgens gekopieerd naar de functieparameter en daarna verwijderd. Dus hier is het beter om het object te verplaatsen, omdat je het anonieme object niet nodig hebt en je tijd en geheugen kunt besparen.

Dit leidt tot het concept van een "rvalue" -referentie. Ze bestaan ​​alleen in C ++ 11 om te detecteren of het ontvangen object anoniem is of niet. Ik denk dat je al weet dat een "lvalue" een toewijsbare entiteit is (het linkerdeel van de = operator), dus u hebt een benoemde verwijzing naar een object nodig om in staat te zijn als een waarde op te treden. Een rwaarde is precies het tegenovergestelde, een object zonder genoemde verwijzingen. Daarom zijn het anonieme object en de rvalue synoniemen. Zo:

class A
{
   int i, *p;

public:
   // Copy
   A(const A& a) : i(a.i), p(new int(*a.p)) {}

   // Movement (&& means "rvalue reference to")
   A(A&& a) : i(a.i), p(a.p)
   {
      a.p = nullptr;
   }

   ~A() { delete p; }
};

In dit geval, wanneer een object van het type A moet worden "gekopieerd", de compiler maakt een lvalue-referentie of een rvalue-verwijzing op basis van of het doorgegeven object een naam heeft of niet. Wanneer dit niet het geval is, wordt je verplaats-constructor aangeroepen en weet je dat het object tijdelijk is en kun je de dynamische objecten verplaatsen in plaats van ze te kopiëren, waardoor je ruimte en geheugen bespaart.

Het is belangrijk om te onthouden dat "statische" objecten altijd worden gekopieerd. Er zijn geen manieren om een ​​statisch object te "verplaatsen" (object in stapel en niet op hoop). Dus het onderscheid "verplaatsen" / "kopiëren" wanneer een object geen dynamische leden heeft (direct of indirect) is niet relevant.

Als uw object complex is en de destructor andere secundaire effecten heeft, zoals bellen naar de functie van een bibliotheek, oproepen naar andere globale functies of wat het ook is, is het misschien beter om een ​​beweging met een vlag aan te geven:

class Heavy
{
   bool b_moved;
   // staff

public:
   A(const A& a) { /* definition */ }
   A(A&& a) : // initialization list
   {
      a.b_moved = true;
   }

   ~A() { if (!b_moved) /* destruct object */ }
};

Uw code is dus korter (u hoeft dit niet te doen nullptr toewijzing voor elk dynamisch lid) en meer algemeen.

Andere typische vraag: wat is het verschil tussen A&& en const A&&? Natuurlijk, in het eerste geval, kunt u het object wijzigen en in de tweede niet, maar, praktische betekenis? In het tweede geval kunt u het niet wijzigen, dus u kunt het object niet ongeldig maken (behalve met een veranderlijke vlag of iets dergelijks) en er is geen praktisch verschil met een kopie-constructor.

En wat is perfecte doorsturing? Het is belangrijk om te weten dat een "rvalue-verwijzing" een verwijzing is naar een genoemd object in het "bereik van de beller". Maar in het werkelijke bereik is een rvalue-verwijzing een naam voor een object, dus deze fungeert als een benoemd object. Als u een rvalue-verwijzing doorgeeft aan een andere functie, geeft u een benoemd object door, dus het object wordt niet ontvangen als een tijdelijk object.

void some_function(A&& a)
{
   other_function(a);
}

Het object a zou worden gekopieerd naar de eigenlijke parameter van other_function. Als je het object wilt a blijft behandeld worden als een tijdelijk object, gebruik dan de std::move functie:

other_function(std::move(a));

Met deze regel std::move zal casten a naar een rwaarde en other_function ontvangt het object als een niet nader genoemd object. Natuurlijk als other_functionheeft geen specifieke overbelasting om met niet nader genoemde objecten te werken, dit onderscheid is niet belangrijk.

Is dat perfect doorsturen? Niet, maar we zijn heel dichtbij. Perfect doorsturen is alleen handig om met sjablonen te werken, met als doel om te zeggen: als ik een object moet doorgeven aan een andere functie, dan heb ik dat nodig als ik een genoemd object ontvang, het object wordt doorgegeven als een benoemd object en wanneer niet Ik wil het doorgeven als een naamloos object:

template<typename T>
void some_function(T&& a)
{
   other_function(std::forward<T>(a));
}

Dat is de handtekening van een prototypische functie die een perfecte doorsturing gebruikt, geïmplementeerd in C ++ 11 door middel van std::forward. Deze functie maakt gebruik van enkele regels voor het maken van sjablonen:

 `A& && == A&`
 `A&& && == A&&`

Dus als T is een lvalue-verwijzing naar A (T = A &), a ook (EEN& && => A &). Als T is een rvalue-verwijzing naar A, a ook (A && && => A &&). In beide gevallen, a is een benoemd object in de werkelijke scope, maar T bevat de informatie van zijn "referentietype" vanuit het oogpunt van de beller. Deze informatie (T) wordt doorgegeven als sjabloonparameter naar forward en 'a' is verplaatst of niet volgens het type T.


19
2017-08-18 15:57



Het lijkt op kopie-semantiek, maar in plaats van alle gegevens te moeten dupliceren, mag je de gegevens stelen van het object dat wordt "verplaatst".


17
2018-06-23 22:56



Weet je wat een kopie-semantiek goed betekent? het betekent dat u typen hebt die kopieerbaar zijn, voor door de gebruiker gedefinieerde typen definieert u dit ofwel koopt u expliciet een copy-constructor en toewijzingsoperator of de compiler genereert ze impliciet. Dit zal een kopie maken.

Verplaatsingssemantie is in feite een door de gebruiker gedefinieerd type met een constructor die een r-waardeverkenning aanneemt (een nieuw type verwijzing met && (ja twee ampersands)) die niet-const is, dit wordt een verplaatsingsconstructor genoemd, hetzelfde geldt voor toewijzingsoperator. Dus wat doet een verhuizersbouwer, nou in plaats van het geheugen van zijn bronargument te kopiëren, 'verplaatst' het geheugen de bron naar de bestemming.

Wanneer zou je dat willen doen? well std :: vector is een voorbeeld, stel dat je een tijdelijke std :: vector hebt gemaakt en je geeft hem terug vanuit een functie zeg:

std::vector<foo> get_foos();

Je zult overhead hebben van de copy constructor wanneer de functie terugkomt, als (en het zal in C ++ 0x) std :: vector een move constructor heeft in plaats van het te kopiëren kan het alleen de pointers en 'move' dynamisch toegewezen krijgen geheugen naar de nieuwe instantie. Het is een soort overdracht van eigendomssemantiek met std :: auto_ptr.


13
2018-06-23 22:58



Om de behoefte aan te illustreren verplaats de semantiek, laten we dit voorbeeld bekijken zonder de semantiek van de verplaatsing:

Hier is een functie die een object van het type neemt T en retourneert een object van hetzelfde type T:

T f(T o) { return o; }
  //^^^ new object constructed

De bovenstaande functie gebruikt call by value wat betekent dat wanneer deze functie wordt aangeroepen een object moet zijn gebouwd door de functie te gebruiken.
Omdat de functie ook retourneert per waarde, een ander nieuw object is geconstrueerd voor de retourwaarde:

T b = f(a);
  //^ new object constructed

Twee er zijn nieuwe objecten geconstrueerd, een daarvan is een tijdelijk object dat alleen wordt gebruikt voor de duur van de functie.

Wanneer het nieuwe object wordt gemaakt op basis van de geretourneerde waarde, wordt de constructor van de kopie aangeroepen kopiëren de inhoud van het tijdelijke object naar het nieuwe object b. Nadat de functie is voltooid, valt het tijdelijke object dat in de functie wordt gebruikt buiten het bereik en wordt het vernietigd.


Laten we nu eens kijken wat een kopieer constructor doet.

Het moet eerst het object initialiseren en vervolgens alle relevante gegevens van het oude object naar de nieuwe kopiëren.
Afhankelijk van de klasse, misschien is het een container met zeer veel gegevens, dan zou dat veel kunnen vertegenwoordigen tijd en geheugengebruik 

// Copy constructor
T::T(T &old) {
    copy_data(m_a, old.m_a);
    copy_data(m_b, old.m_b);
    copy_data(m_c, old.m_c);
}

Met verplaats de semantiek het is nu mogelijk om het meeste van dit werk eenvoudigweg minder onaangenaam te maken in beweging de gegevens in plaats van kopiëren.

// Move constructor
T::T(T &&old) noexcept {
    m_a = std::move(old.m_a);
    m_b = std::move(old.m_b);
    m_c = std::move(old.m_c);
}

Het verplaatsen van de gegevens houdt in dat de gegevens opnieuw worden gekoppeld aan het nieuwe object. En er vindt geen kopie plaats helemaal niet.

Dit wordt bereikt met een rvalue referentie.
Een rvalue referentie werkt vrij veel als een lvalue referentie met één belangrijk verschil:
een rwaarde referentie kan worden verplaatst en een lvalue kan niet.

Van cppreference.com:

Om sterke uitzonderingsgaranties mogelijk te maken, mogen door de gebruiker gedefinieerde verhuisconstructeurs geen uitzonderingen maken. Standaardcontainers vertrouwen doorgaans op std :: move_if_noexcept om te kiezen tussen verplaatsen en kopiëren wanneer containerelementen moeten worden verplaatst.   Als zowel constructeurs voor kopiëren als verplaatsen zijn opgegeven, selecteert de overbelastingsresolutie de verplaatsingsconstructor als het argument een rvalue is (een prvalue zoals een naamloos tijdelijk of een xvalue zoals het resultaat van std :: move) en selecteert de constructor van de kopie als het argument is een lvalue (genoemd object of een functie / operator die lvalue-referentie retourneert). Als alleen de kopieerconstructor wordt opgegeven, selecteren alle argumentcategorieën deze (zolang er een verwijzing naar const nodig is, omdat rwaarden kunnen binden aan const-referenties), waardoor het terugval kopiëren voor verplaatsen, wanneer verplaatsen niet beschikbaar is.   In veel situaties zijn verplaatsingsconstructors geoptimaliseerd, zelfs als ze waarneembare bijwerkingen zouden kunnen veroorzaken, zie kopie elisie.   Een constructor wordt een 'verplaatsingsconstructor' genoemd als een rvalue-referentie als parameter is vereist. Het is niet verplicht om iets te verplaatsen, de klasse hoeft geen resource te verplaatsen en een 'move constructor' kan een resource mogelijk niet verplaatsen zoals in het toegestane (maar misschien niet verstandige) geval waarbij de parameter een const rvalue referentie (const T &&).


6
2018-02-25 00:00



Ik schrijf dit om ervoor te zorgen dat ik het goed begrijp.

Verplaatsingssemantiek werd gemaakt om onnodig kopiëren van grote objecten te voorkomen. Bjarne Stroustrup gebruikt in zijn boek "The C ++ Programming Language" twee voorbeelden waarbij onnodig kopiëren standaard plaatsvindt: een, het omwisselen van twee grote objecten en twee, het retourneren van een groot object van een methode.

Het omwisselen van twee grote objecten omvat meestal het kopiëren van het eerste object naar een tijdelijk object, het kopiëren van het tweede object naar het eerste object en het kopiëren van het tijdelijke object naar het tweede object. Voor een ingebouwd type is dit erg snel, maar voor grote objecten kunnen deze drie exemplaren veel tijd in beslag nemen. Met een "verplaatsingstoewijzing" kan de programmeur het standaardkopieergedrag overschrijven en in plaats daarvan de verwijzingen naar de objecten omwisselen, wat betekent dat er helemaal niet wordt gekopieerd en dat de wisselbewerking veel sneller gaat. De verplaatsingstoewijzing kan worden opgeroepen door de methode std :: move () aan te roepen.

Standaard wordt bij het retourneren van een object uit een methode een kopie gemaakt van het lokale object en de bijbehorende gegevens op een locatie die toegankelijk is voor de beller (omdat het lokale object niet toegankelijk is voor de beller en verdwijnt wanneer de methode is voltooid). Wanneer een ingebouwd type wordt geretourneerd, is deze bewerking erg snel, maar als een groot object wordt geretourneerd, kan dit lang duren. Met de verplaatsingsconstructor kan de programmeur dit standaardgedrag overschrijven en in plaats daarvan de heap-gegevens die aan het lokale object zijn gekoppeld "hergebruiken" door te wijzen op het object dat aan de beller wordt geretourneerd om gegevens te verzamelen die aan het lokale object zijn gekoppeld. Er is dus geen kopie vereist.

In talen die het maken van lokale objecten (dat wil zeggen, objecten op de stapel) niet toestaan, doen dit soort problemen zich niet voor, omdat alle objecten op de heap worden toegewezen en altijd door verwijzing worden benaderd.


3
2017-11-18 23:12