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

Supradefinirea operatorului de atribuire

Compilatorul, în mod implicit, generează un operator de atribuire pentru tipuri, ceea ce ne permite să atribuim valori ale unui anumit tip variabilelor/parametrilor/constantei de același tip. Operatorul de atribuire generat implicit copiază pur și simplu toate variabilele-membră ale clasei una câte una (în ordinea în care sunt declarate în definiția clasei). De exemplu:

#include <iostream>

class Counter
{
public:
    Counter(int val)
    {
        value =val;
    }
    void print()  const
    {
        std::cout << "Value: " << value << std::endl;
    }
    void setValue(int val){ value=val;} // pentru modificarea variabilei value
private:
    int value;
};

int main()
{
    Counter c1{25};
    Counter c2 = c1;    // c2 primește o copie a stării lui c1
    c1.setValue(30);    // modificările în c1 nu afectează c2
    c1.print();     // Value: 30
    c2.print();     // Value: 25
}

Aici este definită clasa Counter, care conține variabila value. Operatorul de atribuire implicit copiază elementele obiectului din partea dreaptă a operatorului de atribuire în obiectul de același tip din partea stângă. Când atribuim obiectul c1 de tip Counter obiectului c2, c2 primește o copie a valorii din c1:

Counter c1{25};
Counter c2 = c1;

Este important de notat că modificarea ulterioară a variabilei value într-un obiect Counter nu va afecta celălalt obiect.

Așa funcționează operatorul de atribuire generat automat. Însă, acesta poate fi suprascris. Trebuie reținut că operatorul de atribuire trebuie definit doar ca funcție-membru a clasei.

Operatorul de atribuire ar trebui să returneze o referință la obiect, iar parametrul său ar trebui să fie o referință la o constantă. Să implementăm un astfel de operator pentru tipul Counter:

#include <iostream>

class Counter
{
public:
    Counter(int val)
    {
        value =val;
    }
    void print()  const
    {
        std::cout << "Value: " << value << std::endl;
    }
    Counter& operator=(const Counter& counter)
    {
        if(&counter != this)
        {
            value = counter.value;
        }
        return *this;
    }
private:
    int value;
};

int main()
{
    Counter c1{25};
    Counter c2{30};
    c2 = c1;    // c2 primește o copie a stării lui c1
    c2.print();     // Value: 25
}

Să analizăm operatorul implementat:

Counter& operator=(const Counter& counter)
{
    if(&counter != this)
    {
        value = counter.value;
    }
    return *this;
}

Funcția operatorului nu este const, deoarece modificăm starea obiectului. Ca parametru, se transmite o referință constantă la obiectul Counter, deoarece acesta nu trebuie modificat.

Ca rezultat, returnăm o referință la obiectul curent Counter. Poate apărea întrebarea: de ce nu returnăm obiectul counter? Deoarece operatorul = este asociativ la dreapta și trebuie să returneze operandul din stânga. De exemplu:

Counter counter1{25};
Counter counter2{0};
Counter counter3{0};
counter3 = counter2 = counter1;

Aici, lanțul de atribuiri se execută astfel:

counter3 = (counter2 = counter1);

Adică mai întâi counter2 primește valoarea lui counter1. Apoi counter3 primește rezultatul operației anterioare, adică obiectul counter2.

Un alt detaliu aparent inutil este verificarea dacă parametrul este același cu obiectul curent:

if(&counter != this)
{
    value = counter.value;
}

Dacă parametrul reprezintă obiectul curent, nu are sens să se efectueze atribuirea. Această verificare previne situații de tipul:

counter1 = counter1;

Ceea ce poate preveni atribuiri inutile și poate îmbunătăți performanța, în special în cazurile care implică alocare/dealocare de memorie, și poate preveni erori legate de scrierea în memorie deja eliberată.

Copierea pointerilor

Uneori, implementarea unui operator de atribuire devine o necesitate. Să analizăm următorul program:

#include <iostream>

class Counter
{
public:
    Counter(int n)
    {
        value =new int{n};    // alocăm memorie
    }
    ~Counter()
    {
        delete value;    // eliberăm memoria
    }
    void print()  const
    {
        std::cout << *value << std::endl;
    }
private:
    int* value;
};

int main()
{
    Counter counter1{5};
    {
        Counter counter2{3};
        counter1 = counter2;
        counter1.print();     // 3
    }
    counter1.print();     // ????
}

În blocul interior este creat obiectul counter2, care este atribuit obiectului counter1. Acest lucru duce la copierea adresei pointerului, astfel încât obiectele counter1 și counter2 vor indica aceeași adresă în memorie. Dar, la finalul blocului interior, se încheie domeniul de vizibilitate al obiectului counter2, iar destructorul său este apelat, eliberând memoria.

Prin urmare, memoria folosită de counter1 este și ea eliberată, deoarece era aceeași zonă de memorie. Însă counter1 continuă să existe, fiind declarat în afara blocului. În final, ne vom confrunta cu un comportament imprevizibil.

Pentru a ieși din această situație, putem defini operatorul de atribuire astfel:

#include <iostream>

class Counter
{
public:
    Counter(int n)
    {
        value = new int{n};    // alocăm memorie
    }
    ~Counter()
    {
        delete value;    // eliberăm memoria
    }
    void print() const
    {
        std::cout << *value << std::endl;
    }
    Counter& operator=(const Counter& counter)
    {
        if(&counter != this)
        {
            *value = *counter.value;
        }
        return *this;
    }
private:
    int* value;
};

int main()
{
    Counter counter1{5};
    {
        Counter counter2{3};
        counter1 = counter2;
        counter1.print();     // 3
    }
    counter1.print();     // 3
}

Ștergerea operatorului de atribuire

În situația precedentă există o alternativă la implementarea operatorului de atribuire — ștergerea operatorului de atribuire implicit. Pentru aceasta se folosește cuvântul cheie delete:

Counter& operator=(const Counter& counter) = delete;

În acest caz, dacă în program se va încerca utilizarea operatorului de atribuire, de tipul:

counter1 = counter2;

atunci compilatorul va genera o eroare.