Sobes.tech
Middle+

Wat is verplaatsingssemantiek en hoe gebruik je het?

sobes.tech AI

Antwoord van AI

Move semantics (verhuissemantiek) in C++11 en hoger stelt je in staat om het eigenaarschap van een resource efficiënt van het ene object naar het andere over te dragen, waardoor onnodige kopieën worden vermeden. Dit wordt bereikt door gebruik te maken van rvalue-referenties (&&) en de move-operator (std::move).

Belangrijke aspecten:

  • rvalue-referenties (&&): Referenties naar tijdelijke objecten (d.w.z. rvalues) of naar verplaatsbare objecten. Ze maken het mogelijk om zich te binden aan een object dat binnenkort wordt vernietigd.
  • Move-operator (std::move): Zet een lvalue om in een rvalue-referentie. Voert zelf geen verplaatsing uit, maar geeft aan de compiler aan dat het object veilig kan worden verplaatst.
  • Move-constructeur: Een speciale constructor die een rvalue-referentie als argument accepteert. "Steelt" resources (bijvoorbeeld pointers naar dynamisch geheugen) van het bronobject, en laat het in een geldige maar geleegde staat achter.
  • Move-toewijzingsoperator: Vergelijkbaar met de move-constructeur, bedoeld voor toewijzingsbewerkingen.

Toepassingen:

  1. Optimalisatie: Vermindert de kosten van het kopiëren van grote objecten of objecten die resources bezitten (bijvoorbeeld containers, strings, slimme pointers).
  2. Implementatie van efficiënte containers: Maakt het mogelijk voor containers zoals std::vector om effectief te beheren bij het wijzigen van de grootte of het verplaatsen.
  3. Teruggeven van grote objecten per waarde: De compiler kan automatisch de move-optimisatie toepassen (return value optimization, RVO, of Named Return Values Optimization, NRVO), als het geretourneerde object een lokale variabele of een tijdelijk object is.

Voorbeeld:

#include <iostream>
#include <vector>
#include <string>
#include <utility> // Voor std::move

class MyResource {
public:
    std::vector<int> data;

    // Constructor
    MyResource(size_t size) : data(size) {
        std::cout << "Constructor" << std::endl;
    }

    // Destructor
    ~MyResource() {
        std::cout << "Destructor" << std::endl;
    }

    // Copy constructor
    MyResource(const MyResource& other) : data(other.data) {
        std::cout << "Copy constructor" << std::endl;
    }

    // Copy assignment operator
    MyResource& operator=(const MyResource& other) {
        if (this != &other) {
            data = other.data;
        }
        std::cout << "Copy assignment operator" << std::endl;
        return *this;
    }

    // Move constructor
    MyResource(MyResource&& other) noexcept : data(std::move(other.data)) {
        std::cout << "Move constructor" << std::endl;
        // Na het verplaatsen, bevindt het originele object zich in een geldige maar lege staat
        // other.data blijft geldig, maar de inhoud kan zijn verplaatst
    }

    // Move assignment operator
    MyResource& operator=(MyResource&& other) noexcept {
        if (this != &other) {
            data = std::move(other.data);
        }
        std::cout << "Move assignment operator" << std::endl;
        return *this;
    }
};

int main() {
    MyResource res1(10); // Constructor
    std::cout << "---" << std::endl;
    MyResource res2 = res1; // Copy constructor of operator
    std::cout << "---" << std::endl;
    MyResource res3 = std::move(res1); // Move constructor
    std::cout << "---" << std::endl;
    MyResource res4(5); // Constructor
    res4 = std::move(res3); // Move assignment operator
    std::cout << "---" << std::endl;

    return 0;
}

In dit voorbeeld wordt duidelijk dat bij gebruik van std::move de move-constructeur of move-toewijzingsoperator wordt aangeroepen in plaats van hun kopie-varianten, waardoor kostbare kopieën van de data-vector worden vermeden.