Diferență între revizuiri ale paginii „C++ POO Operatorii new si delete”

De la WikiLabs
Jump to navigationJump to search
 
Linia 476: Linia 476:
 
     }
 
     }
  
 +
    // constructor de copiere
 
     Text(const Text& other) {
 
     Text(const Text& other) {
 
         s = new char[strlen(other.s) + 1];
 
         s = new char[strlen(other.s) + 1];
 
         strcpy(s, other.s);
 
         strcpy(s, other.s);
 +
    }
 +
 +
    // operatorul de atribuire
 +
    Text& operator=(const Text& other) {
 +
        // verifica daca nu se incearca autoatribuire
 +
        if (this != &other) {
 +
            // elibereaza memoria veche
 +
            delete[] s;
 +
            // aloca memorie noua si copiaza datele
 +
            s = new char[strlen(other.s) + 1];
 +
            strcpy(s, other.s);
 +
        }
 +
        return *this;
 
     }
 
     }
  
Linia 491: Linia 505:
  
 
int main() {
 
int main() {
     Text t1("C++");
+
     Text t1("Laborator");
     Text t2 = t1;
+
     Text t2("Initial");
 +
 
 +
    t2 = t1;
  
 
     t1.afiseaza();
 
     t1.afiseaza();

Versiunea curentă din 24 martie 2026 13:18

[WIP Costin] Pagina este in lucru!

Introducere

În programele scrise până acum, majoritatea variabilelor și obiectelor au fost declarate în mod direct, de exemplu:

int x;
Persoana p;

În aceste cazuri, memoria necesară este rezervată automat la intrarea în blocul în care au fost declarate, iar eliberarea ei are loc tot automat la ieșirea din acel bloc. Acest mod de lucru este simplu și sigur, dar nu este întotdeauna suficient.

Există numeroase situații în care un program nu poate ști dinainte de câtă memorie are nevoie. De exemplu:

  • numărul de elemente dintr-un vector este citit de la tastatură;
  • un obiect trebuie să existe și după terminarea funcției în care a fost creat;
  • o clasă trebuie să gestioneze intern resurse a căror dimensiune este variabilă;
  • programul trebuie să creeze obiecte dinamic, doar atunci când este nevoie de ele

În astfel de situații apare nevoia de alocare dinamică a memoriei, adică rezervarea memoriei în timpul execuției programului. În C++, acest lucru se realizează cu ajutorul operatorilor new și delete.

Operatorul new

Operatorul new este folosit pentru a rezerva memorie dinamică și, în cazul obiectelor, pentru a construi obiectul în acea zonă de memorie (prin apelarea constructorului).


Forma generală pentru new este următoarea:

<tip_de_date> <nume_pointer> = new <tip_de_date>;

Această linie:

  • rezervă memorie pentru o variabilă de tipul specificat;
  • returnează adresa acelei zone de memorie;
  • memorează adresa într-un pointer de tip corespunzător


Dacă se dorește și inițializare:

<tip_de_date> <nume_pointer> = new <tip_de_date>(<valoare_initiala>);

În plus față de varianta anterioară, această linie inițializează cu <valoare_initiala> valoarea stocată în memoria alocată.

Operatorul delete

Operatorul delete este folosit pentru a distruge obiectul creat dinamic (prin apelarea destructorului) și pentru a elibera memoria ocupată de acesta.

Forma generală pentru delete este următoarea:

delete <nume_pointer>;
<nume_pointer> = nullptr;

Alocarea dinamică pentru tipuri simple

În această secțiune vor fi prezentate câteva exemple comune de utilizare a operatorilor new și delete cu tipuri de date simple.

Exemplul 1: alocare dinamică pentru un element de tip int

// declară un pointer p către int si alocă dinamic memorie pentru un număr întreg
int* p = new int; 
// stocheaza valoarea 10 la adresa stocata in pointerul p
*p = 10; 

// afișează valoarea stocată la adresa din pointerul p
cout << *p << endl; 

// memoria alocată anterior este eliberată
delete p;

Exemplul 2: alocare cu inițializare

// declară un pointer p către int si alocă dinamic memorie pentru un număr întreg
//    și inițializează valoarea de la acea adresă cu 25
int* p = new int(25);

cout << *p << endl;

delete p;

Exemplul 3: alocare cu alte tipuri simple

double* x = new double(3.14);
char* c = new char('A');

cout << *x << endl;
cout << *c << endl;

delete x;
delete c;

Alocarea dinamică a vectorilor de date simple

Sintaxa generală este următoarea:

// new
<tip_de_date>* <nume_pointer> = new <tip_de_date>[<numar_elemente>];

// delete
delete[] <nume_pointer>;
Atenție: pentru memoria alocată dinamic sub forma unui vector, folosind new[], eliberarea trebuie făcută întotdeauna cu delete[] și nu cu delete. Folosirea lui delete simplu în acest caz este greșită și conduce la comportament nedefinit, deoarece programul nu mai tratează corect memoria rezervată pentru toate elementele vectorului.

Exemplul 1: alocarea dinamică a unui vector

int n = 5;
int* v = new int[n]; // aloca memorie pentru un vector de int cu 5 elemente

Exemplul 2: suma numerelor dintr-un vector

// se declara n si se citeste de la tastatura dimensiunea vectorului
int n;
cin >> n;

// se aloca dinamic vectorul de n elemente
int* v = new int[n];

// se citesc de la tastatura elementele vectorului
for (int i = 0; i < n; i++)
    cin >> v[i];

// se calculeaza suma tuturor elementelor din vector
int suma = 0;
for (int i = 0; i < n; i++)
    suma += v[i];

// afisarea sumei
cout << "Suma = " << suma << endl;

// se elibereaza memoria alocata anterior
delete[] v;
v = nullptr;


Alocarea dinamică a obiectelor

La fel ca la tipurile de date simple, sintaxa generală este următoarea:

<numa_clasa>* <nume_pointer> = new <nume_clasa>;

Această linie va aloca memorie pentru noul obiect și va apela constructorul fără argumente.

Dacă dorim să apelăm constructorul cu parametri, vom folosi următoarea sintaxa:

<numa_clasa>* <nume_pointer> = new <nume_clasa>(<argumente_constructor>);

Pentru distrugerea obiectului se foloseste in continuare:

delete <nume_pointer>;

Exemplul 1: obiect creat dinamic folosind constructorul implicit

#include <iostream>
using namespace std;

class Punct {
    int x, y;
public:
    Punct() {
        x = 0;
        y = 0;
        cout << "Constructor implicit\n";
    }

    ~Punct() {
        cout << "Destructor\n";
    }

    void afiseaza() {
        cout << "(" << x << ", " << y << ")\n";
    }
};

int main() {
    // alocă memorie pentru un obiect de tip Punct și apelează constructorul implicit al clasei
    Punct* p = new Punct;
    // deoarece avem un pointer la obiect, metodele si campurile sale sunt accesate cu ->
    p->afiseaza();
    // apelează destructorul obiectului și apoi eliberează memoria ocupată de el
    delete p;

    return 0;
}

Exemplul 2: obiect creat dinamic folosind un constructor cu parametri

#include <iostream>
using namespace std;

class Punct {
    int x, y;
public:
    Punct(int a, int b) {
        x = a;
        y = b;
        cout << "Constructor cu parametri\n";
    }

    ~Punct() {
        cout << "Destructor\n";
    }

    void afiseaza() {
        cout << "(" << x << ", " << y << ")\n";
    }
};

int main() {
    // aloca memorie si apeleaza constructorul cu parametri
    Punct* p = new Punct(3, 4);
    // deoarece avem un pointer la obiect, metodele si campurile sale sunt accesate cu ->
    p->afiseaza();
    // apelează destructorul obiectului și apoi eliberează memoria ocupată de el
    delete p;
    return 0;
}

Exemplul 3: alocarea dinamică și constructorul de copiere

#include <iostream>
using namespace std;

class Punct {
    int x, y;
public:
    Punct(int a, int b) {
        x = a;
        y = b;
        cout << "Constructor cu parametri\n";
    }

    Punct(const Punct& other) {
        x = other.x;
        y = other.y;
        cout << "Constructor de copiere\n";
    }

    ~Punct() {
        cout << "Destructor\n";
    }

    void afiseaza() {
        cout << "(" << x << ", " << y << ")\n";
    }
};

int main() {
    // obiectul p1 este creat obisnuit, apeland constructorul cu argumentr
    Punct p1(1, 2);
    // creează dinamic un nou obiect folosind constructorul de copiere
    Punct* p2 = new Punct(p1);

    p2->afiseaza();

    delete p2;
    return 0;
}

Diferența dintre new și malloc

În limbajul C, alocarea dinamică a memoriei se face în mod obișnuit cu funcția malloc. În C++, aceasta există în continuare pentru compatibilitate, dar pentru obiecte și clase nu este soluția potrivită.

Dacă alocăm un obiect cu new:

Test* p = new Test;

se întâmplă două lucruri:

  • se rezervă memorie pentru obiect;
  • se apelează constructorul obiectului

Dacă încercăm să alocăm memorie pentru același tip folosind malloc:

Test* p = (Test*)malloc(sizeof(Test));

se rezervă doar memorie brută. Constructorul nu este apelat. Cu alte cuvinte, malloc știe doar să rezerve un număr de octeți, dar nu știe nimic despre noțiunea de obiect, constructor sau clasă.

Aceasta este diferența fundamentală:

  • malloc alocă memorie;
  • new alocă memorie și construiește obiectul.

Alocarea dinamică a vectorilor de obiecte

În cazul unui vector de obiecte, operatorul new[] rezervă memorie pentru toate elementele vectorului și apelează constructorul corespunzător pentru fiecare obiect, iar operatorul delete[] apelează destructorul fiecărui obiect și apoi eliberează memoria ocupată de întregul vector.

La fel ca la vectorii de date simple, sintaxa generală este următoarea:

<nume_clasa>* <nume_vector> = new <nume_clasa>[<dimensiune>];

Exemplul 1: vector dinamic de obiecte folosind constructorul implicit

#include <iostream>
using namespace std;

class Punct {
    int x, y;
public:
    Punct() {
        x = 0;
        y = 0;
        cout << "Constructor implicit\n";
    }

    ~Punct() {
        cout << "Destructor\n";
    }

    void afiseaza() {
        cout << "(" << x << ", " << y << ")\n";
    }
};

int main() {
    // creează un vector dinamic cu 3 obiecte de tip Punct
    // Pentru fiecare element din vector este apelat constructorul implicit
    Punct* v = new Punct[3];

    for (int i = 0; i < 3; i++)
        v[i].afiseaza(); // Accesul la elementele vectorului se face ca la un vector obișnuit:

    // apelează destructorul pentru fiecare dintre cele 3 obiecte și apoi eliberează memoria vectorului
    delete[] v;
    return 0;
}

Exemplul 2: modificarea elementelor din vector

#include <iostream>
using namespace std;

class Punct {
    int x, y;
public:
    Punct() {
        x = 0;
        y = 0;
    }

    ~Punct() {
        cout << "Destructor pentru punctul (" << x << ", " << y << ")\n";
    }

    void set(int a, int b) {
        x = a;
        y = b;
    }

    void afiseaza() {
        cout << "(" << x << ", " << y << ")\n";
    }
};

int main() {
    int n = 4;
    Punct* v = new Punct[n];

    for (int i = 0; i < n; i++)
        v[i].set(i, i * 10);

    for (int i = 0; i < n; i++)
        v[i].afiseaza();

    delete[] v;
    return 0;
}

Obiecte care conțin memorie alocată dinamic

Un astfel de obiect nu mai conține doar date simple, ci și un pointer către o zonă de memorie alocată dinamic. În acest caz, clasa trebuie să gestioneze corect această resursă: să o aloce atunci când obiectul este creat, să o elibereze atunci când obiectul este distrus și să o copieze corect atunci când obiectul este copiat sau atribuit altuia.

Exemplul 1: un obiect care deține memorie dinamică

#include <iostream>
#include <cstring>
using namespace std;

class Text {
    // clasa Text conține un pointer la char
    char* s;
public:
    Text(const char* p) {
        // constructorul aloca memorie suficienta pentru pointerul s
        s = new char[strlen(p) + 1];
        // apoi se face copierea element cu element (in cazul acesta folosind strcpy)
        //     atribuirea directa s = p ar fi fost gresita; in acest caz s-ar copia pointerul, nu valorile catre care trimite acesta
        strcpy(s, p);
    }

    ~Text() {
        // la distrugerea obiectului, memoria alocata in constructor trebuie eliberată
        delete[] s;
    }

    void afiseaza() {
        cout << s << endl;
    }
};

int main() {
    Text t("Laborator C++");
    t.afiseaza();
    return 0;
}

Exemplul 2: La distrugerea obiectului, memoria trebuie eliberată

#include <iostream>
#include <cstring>
using namespace std;

class Text {
    char* s;
public:
    Text(const char* p) {
        s = new char[strlen(p) + 1];
        strcpy(s, p);
    }

    ~Text() {
        delete[] s;
    }

    void afiseaza() {
        cout << s << endl;
    }
};

int main() {
    Text t1("C++");
    // copiere superficiala (shallow copy)
    Text t2 = t1;

    t1.afiseaza();
    t2.afiseaza();

    return 0;
}

La prima vedere, codul pare corect. Totuși, el ascunde o problemă gravă. Pentru că noi nu am definit un constructor de copiere, compilatorul generează automat unul implicit. Acesta copiază fiecare membru „ca atare”. În consecință, pointerul s din t2 va primi exact aceeași adresă ca pointerul s din t1.

Cu alte cuvinte, după copiere:

  • t1.s și t2.s indică spre aceeași zonă de memorie;
  • nu există două copii ale textului, ci doar două pointere către același text.

Aceasta se numește copiere superficială (shallow copy).

Dacă două obiecte indică spre aceeași zonă de memorie, atunci la distrugerea lor fiecare destructor va încerca să elibereze aceeași zonă:

  • destructorul lui t2 face delete[] s;
  • destructorul lui t1 face din nou delete[] s.

Rezultatul este o dublă eliberare a memoriei, ceea ce produce comportament nedefinit.

Prin urmare, pentru clasele care conțin pointeri spre memorie alocată dinamic, copierea superficială este greșită.

Exemplul 3: copiere profundă

Pentru a evita problema copierii superficiale, fiecare obiect trebuie să aibă propria sa copie a datelor. Cu alte cuvinte, la copiere nu trebuie copiată doar adresa, ci trebuie alocată o nouă zonă de memorie și copiat conținutul în ea.

Aceasta se numește copiere profundă (deep copy).

#include <iostream>
#include <cstring>
using namespace std;

class Text {
    char* s;
public:
    Text(const char* p) {
        s = new char[strlen(p) + 1];
        strcpy(s, p);
    }

    // constructor de copiere
    Text(const Text& other) {
        s = new char[strlen(other.s) + 1];
        strcpy(s, other.s);
    }

    // operatorul de atribuire
    Text& operator=(const Text& other) {
        // verifica daca nu se incearca autoatribuire
        if (this != &other) {
            // elibereaza memoria veche
            delete[] s;
            // aloca memorie noua si copiaza datele
            s = new char[strlen(other.s) + 1];
            strcpy(s, other.s);
        }
        return *this;
    }

    ~Text() {
        delete[] s;
    }

    void afiseaza() {
        cout << s << endl;
    }
};

int main() {
    Text t1("Laborator");
    Text t2("Initial");

    t2 = t1;

    t1.afiseaza();
    t2.afiseaza();

    return 0;
}