MySQL Java JavaScript PHP Python HTML-CSS C-sharp C++ Go

Idiomă Move-and-Swap / Mutare cu schimb

Idiomul move-and-swap sau mutarea cu schimb este utilizat în operatorii de atribuire cu mutare. Acesta ajută la evitarea duplicării codului din destructor și constructorul de copiere. Esența acestei idiome este următoarea:

  • Pentru obiectul care este mutat, creăm o copie folosind constructorul de mutare
  • Înlocuim obiectul curent cu copia modificată. Dacă în timpul modificării copiei apare o eroare, obiectul curent nu este înlocuit

Forma generală a idiomei move-and-swap arată astfel:

MyClass& MyClass::operator=(MyClass&& rhs) noexcept
{
    MyClass moved(std::move(rhs)); // obținem obiectul mutat
    swap(moved);                  // efectuăm schimbul de valori
    return *this; // returnăm obiectul curent
}

Să analizăm o implementare simplă cu un exemplu:

#include <iostream> 

class Message {
public:
    Message(std::string data) : text{new std::string(data)} { // alocăm memorie
        id = ++counter;
        std::cout << "Create message " << id << std::endl;
    }

    // constructor de copiere
    Message(const Message& copy) : Message{copy.getText()} {
        std::cout << "Copy message " << copy.id << " to " << id << std::endl;
    }

    // constructor de mutare
    Message(Message&& moved) noexcept : text{moved.text} {
        id = ++counter;
        std::cout << "Move message " << moved.id << " to " << id << std::endl;
        moved.text = nullptr;
    }

    ~Message() {
        std::cout << "Delete message " << id << std::endl;
        delete text;    // eliberăm memoria
    }

    // atribuire prin copiere
    Message& operator=(const Message& copy) {
        std::cout << "Copy assign message " << copy.id << " to " << id << std::endl;
        if (&copy != this) { // evităm auto-atribuirea
            *text = copy.getText();
        }
        return *this; 
    }

    // atribuire prin mutare
    Message& operator=(Message&& moved) noexcept {
        std::cout << "Move assign message " << moved.id << " to " << id << std::endl;
        Message temp{std::move(moved)}; // apelăm constructorul de mutare
        swap(temp);                     // schimbăm valorile
        return *this; // returnăm obiectul curent
    }

    // funcția de schimb
    void swap(Message& other) noexcept {
        std::swap(text, other.text);    // schimbăm cele două pointere
    }

    std::string& getText() const { return *text; }
    unsigned getId() const { return id; }

private:
    std::string* text;  // textul mesajului
    unsigned id{};  // numărul mesajului
    static inline unsigned counter{};   // contor static pentru generarea numărului obiectului
};

int main() {   
    Message mes{""};
    mes = Message{"hello"};     // atribuire prin mutare
    std::cout << "Message " << mes.getId() << ": " << mes.getText() << std::endl;
}

Explicația programului:

1) În constructorul Message, se alocă memorie dinamică pentru a stoca un obiect de tipul std::string, iar în destructor, memoria este eliberată.

2) Se creează un nou obiect folosind constructorul de copiere și se copiază datele. Aici se folosește delegarea constructorului pentru a evita duplicarea logicii.

3) Se mută pointerul la text dintr-un obiect într-altul. După mutare, obiectul sursă este setat la nullptr pentru a preveni utilizarea incorectă a memoriei.

4) Operatorul de atribuire copiază datele dintr-un alt obiect, iar înainte de a face acest lucru, verifică dacă obiectul curent este diferit de obiectul care trebuie copiat (evitând auto-atribuirea).

5) În acest operator, se folosește idiomul move-and-swap. Se creează un obiect temporar folosind constructorul de mutare și apoi se face schimbul între obiectul curent și cel temporar. Dacă ceva nu merge bine, obiectul curent nu este afectat.

6) Această funcție schimbă pur și simplu două pointere, ceea ce permite schimbul valorilor între două obiecte.

Ieșirea programului:

Create message 1
Create message 2
Move assign message 2 to 1
Move message 2 to 3
Delete message 3
Delete message 2
Message 1: hello
Delete message 1

Explicație:

1) Obiectul mes este creat cu textul gol.

2) Când se execută atribuirea mes = Message{"hello"};, un nou obiect Message cu textul "hello" este creat (cu numărul 2).

3) În cadrul operatorului de atribuire cu mutare, se creează un obiect temporar și se mută datele din obiectul mes în acest obiect temporar.

4) Apoi se face schimbul între obiectul curent și obiectul temporar. După schimb, obiectul temporar este distrus, iar memoria este eliberată.

Astfel, idiomul move-and-swap ajută la crearea operatorilor de atribuire eficienți și siguri, minimizând riscurile de scurgeri de memorie și alte erori asociate cu gestionarea resurselor.