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

Gestionarea resurselor - Idioma RAII

Obiectele claselor pot utiliza diverse resurse pe durata existenței lor, cum ar fi memoria dinamică alocată, fișierele, conexiunile la rețea etc. În C++, se folosește principiul/idioma RAII (resource acquisition is initialization). RAII presupune că achiziționarea unei resurse se face la inițializarea obiectului, iar eliberarea resursei se face în destructorul obiectului.

#include <iostream>

class IntArray
{
public:
    IntArray(unsigned size) : data{ new int[size] } {}  // alocăm memorie
    ~IntArray()
    {
        if (data)
        {
            std::cout << "Freeing memory..." << std::endl;
            delete[] data;      // eliberăm memoria
        }
    }
    // Eliminăm constructorul de copiere și operatorul de atribuire
    IntArray(const IntArray&) = delete;
    IntArray& operator=(const IntArray&) = delete;

    // operatorul de indexare pentru accesul la elemente
    int& operator[](unsigned index) { return data[index]; }

    // returnăm resursa încapsulată
    int* get() const { return data; }

    // transferăm resursa altui obiect
    int* release()
    {
        int* result = data;
        data = nullptr;
        return result;
    }

private:
    int* data;
};

int main()
{   
    const unsigned count {5};   // numărul de elemente
    IntArray values{count};     // creăm un obiect care gestionează resursa

    // modificăm elementele dinamicii array
    for (unsigned i {}; i < count; ++i)
    {
        values[i] = i;
    }
    // afișăm elementele dinamicii array pe consolă
    for (unsigned i {}; i < count; ++i)
    {
        std::cout << values[i] << "\t";
    }
    std::cout << std::endl;
}

Aici, este definită clasa IntArray, care reprezintă un tablou de numere int și care gestionează o resursă. În acest caz, resursa reprezintă memoria dinamică alocată pentru a stoca tabloul de numere int. Obținerea memoriei dinamice se face în constructorul obiectului, iar eliberarea acesteia se face în destructor.

IntArray(unsigned size) : data{ new int[size] } {}  // alocăm memorie
~IntArray()
{
    std::cout << "Freeing memory..." << std::endl;
    delete[] data;      // eliberăm memoria
}

Este important de menționat că memoria dinamică reprezintă un caz special al resurselor (în realitate, aceste resurse pot fi fișiere, conexiuni la rețea etc.) și este utilizată aici în scop demonstrativ, deoarece în loc să alocăm și să eliberăm memoria, în astfel de situații putem utiliza pointeri inteligenți.

Totodată, este important ca resursa (în acest caz, memoria dinamică) să fie eliberată o singură dată. Pentru acest scop, în clasă sunt șterse constructorul de copiere și operatorul de atribuire, evitând astfel situațiile în care două obiecte ar stoca un pointer către aceeași zonă de memorie dinamică și, în consecință, ar încerca să elibereze acea memorie în destructor.

IntArray(const IntArray&) = delete;
IntArray& operator=(const IntArray&) = delete;

Pentru a accesa elementele tabloului dinamic, este definit operatorul de indexare [], iar pentru a obține direct pointerul - funcția get.

Merită menționată funcția release(), care permite transferul pointerului către alt obiect sau către un alt cod. În acest caz, pointerul este resetat la nullptr, iar responsabilitatea de a elibera memoria revine codului exterior care obține acest pointer:

int* release()
{
    int* result = data;
    data = nullptr;
    return result;
}

În funcția main, se creează un obiect IntArray:

int main()
{   
    const unsigned count {5};   // numărul de elemente
    IntArray values{count};     // creăm un obiect care gestionează resursa

Astfel, în constructor se alocă memoria, iar după finalizarea funcției main, pentru obiectul IntArray se va apela destructorul, care va elibera memoria. Ieșirea în consolă a programului va fi:

0       1       2       3       4
Freeing memory...

Acum să vedem cum se aplică funcția release():

int main()
{   
    const unsigned count {5};   // numărul de elemente
    IntArray array{count};     // creăm un obiect care gestionează resursa

    // modificăm elementele dinamicii array
    for (unsigned i {}; i < count; ++i)
    {
        array[i] = i;
    }

    // obținem pointerul
    int* data = array.release(); // acum funcția main trebuie să elibereze memoria

    for (unsigned i {}; i < count; ++i)
    {
        std::cout << data[i] << "\t";
    }
    std::cout << std::endl;

    // eliberăm memoria
    delete[] data;
}

Aici, pointerul către tabloul dinamic este transferat în variabila data prin funcția release. După aceea, funcția main` devine responsabilă pentru eliberarea memoriei, ceea ce se întâmplă la sfârșitul funcției.