Sari la conținut

RAII

De la Wikipedia, enciclopedia liberă

Resource Acquisition Is Initialization (achiziția resurselor prin inițializare[1]), des întâlnită în cărțile de specialitate sub acronimul RAII,[2] este un tip de design al aplicațiilor dezvoltate în limbaje orientate pe obiect, precum C++, D sau Ada. Tehnica a fost inventată de Bjarne Stroustrup, pentru a rezolva problema dealocării memoriei în C++.

În cazul C++, singurele segmente cod ce se vor executa garantat dupa ce apare o excepție vor fi destructorii obiectelor aflate în stivă. Astfel, ne va trebui o modalitate prin care să atașăm resursele de obiectele cu durata de viață adecvată. Resursele pot fi obținute în timpul inițializării, atunci când nu există șansa ca acestea să fie utilizate înainte de a fi disponibile, și apoi eliberate odată cu distrugerea respectivului obiect, fapt ce poate fi garantat că se va întampla, inclusiv în cazul apariției unei erori. RAII este vital dacă dorim un cod C++ securizat contra excepțiilor [3]– pentru a eliberara memoria înaintea propagării excepției vor trebui doar scriși destructorii necesari o singura dată, comparativ cu cazul dispersării și duplicării intrucțiunilor de „curățare” încadrate între blocuri de tratare a excepțiilor ce nu este garantat ca se vor executa.
Tehnica RAII este des utilizată în controlul mutex-urilor în aplicații multi-threaded, în care caz obiectul va elibera resursa blocată atunci când acesta va fi distrusă. Fără RAII, probabilitatea unui deadlock creste exponențial. O alta aplicație este managementul fișierelor - clasa ce se ocupa de aceasta va inchide fișierul atunci când aceasta va fi distrusă.

Exemplu de implementare RAII in C++

[modificare | modificare sursă]

Pasul 1: Inițializarea

[modificare | modificare sursă]

Un exemplu[4] simplu de implementare a metodei RAII începe cu o clasă ce automatizează operațiile de alocare și dealocare a memoriei. În cazul acesta, este vorba de un singur tip de date: un șir de caractere a cărui mărime este cunoscută prin parametrul constructorului clasei.

class mem
{
public: 
 mem(unsigned int size=0); // serves as a default ctor, too
private:
 char * pdata
 //…
};

mem::mem(unsigned int size)
{
 if (size)
  pdata = new char[size];
 else pdata=0; // if size equals 0, defer allocation
}

Constructorul permite ca utilizatorul să amâne alocarea memoriei prin transmiterea argumentului de valoare zero. Acest principiu de design este important, deoarece, cu toate că operația în sine este opțională, execuția constructorului nu este. Alt argument pentru a da mărimii o valoare implicită este pentru a oferi posibilitatea creării unui sir de obiecte tip mem.

Pasul 2: Eliberarea

[modificare | modificare sursă]

Destructorul clasei mem nu are nici o particularitate. Chiar dacă utilizatorul nu a alocat memorie la apelarea constructorului, ștergerea unui pointer NULL nu întoarce nici o eroare, deci nu vor exista probleme.

mem::~mem()
{
 delete [] pdata;
}

Alternative RAII

[modificare | modificare sursă]

Pointeri partajați

[modificare | modificare sursă]

Dupa cum am mai precizat mai sus, o funcționalitate extrem de importantă pentru RAII este controlul memoriei alocate dinamic – utilizând instructiunea new – deoarece memoria va fi eliberată atunci când obiectul RAII va fi distrus. Pentru acest scop, sunt incluși în TR1 (și marcați pentru a fi incluși în urmatorul standard C++0x), [[5] tip smart pointer⁠(d) ce au implementata semantica de memorie comun alocată (shared-memory).
Pe scurt, pointerii partajați sunt obiecte C++ ce simulează comportamentul pointerilor clasici, prin suprascrierea operatorilor -> și *, împreună cu o serie de alte instrucțiuni. Pentru a exemplifica implementarea standard, ne vom referii la clasa shared_ptr, așa cum este definită în libraria Boost⁠(en)[traduceți] (și cum va fi inclusa în urmatorul standard C++0x. Clasa șablon shared_ptr stochează un pointer către un obiect alocat dinamic , acesta fiind garantat eliminat atunci când ultimul shared_ptr către acesta va fi distrus sau reinițializat. Implementarea implică existenta unui iterator, ce va ține gestiunea numărului de referințe către respectivul obiect. Spre exemplu, aceasta este un fragment de cod simplu, ce implementează ștergerea unui șir de elemente, utiliznd un shared pointer:

class ArrayDeleter
{
public:
    void operator () (T* d) const
    {
        delete [] d;
    }
};
 
int main ()
{
    std::tr1::shared_ptr array (new double [256], ArrayDeleter ());
}

Comparând acest fragment de cod[6] cu exemplul de implementare dat mai sus, se observă cum includerea smart pointerilor are efectul dorit pentru proiectarea unei aplicații dupa metoda RAII, cu reducerea codului scris în mod considerabil.

Automatizarea eliberării memoriei

[modificare | modificare sursă]

Cunoscută în cărțile de specialitate sub numele de "colectare a gunoiului" (garbage collection sau, pe scurt, GC⁠(en)[traduceți]), automatizarea gestionării memoriei, este un mecanism de reciclare a obiectelor nereferențiate în timp real, însă nu oferă garanția că va controla distrugerea obiectelor. Cu toate că limbaje de programare precum C# si Java au nativ un astfel de sistem, implementarea lui într-o aplicație C++ anulează tocmai avantajele utilizării C++ : un management al resurselor determinist. Utilizatorul nu poate știi niciodată cand se va declansa sistemul de eliberare a memoriei sau dacă un obiect este referențiat fără a fi utilizat (deci nu previne erori de tip memory leak⁠(d) ). Astfel, în C++, nu există motive viabile de a înlocui un sistem RAII cu un GC[7].

Managementul manual al eliberării memoriei

[modificare | modificare sursă]

Presupune că programatorul va împânzi codul cu instrucțiuni new și delete, de cele mai multe ori încapsulate între blocuri de try/catch. Aceasta este abordarea de bază și funcționează în aplicații mici, pentru care efortul de implementare a unui sistem RAII sau chiar folosirea smart pointerilor nu se justifică. Însa, atunci când codul crește în dimensiune, obiectele se înmulțesc la număr și/sau se dorește execuție de tip multithreaded, redundanța codului va creste exponențial și la fel și numărul erorilor.

Astfel, citând cuvintele lui Stroustrup: „Aplicând aceasta tehnică în mod recursiv și în cât mai multe locuri posibil în cadrul aplicației tale, alocările și dealocările vor dispărea aproape total din cod.”

  1. ^ Andrei Alexandrescu - „Modern C++ Design: Generic Programming and Design Patterns Applied”
  2. ^ Stroustrup, Bjarne (1994). „The Design and Evolution of C++”
  3. ^ Roland Pibinger - "RAII, Dynamic Objects, and Factories în C++"
  4. ^ „copie arhivă”. Arhivat din original la . Accesat în . 
  5. ^ shared_ptr - 1.46.1, www.boost.org 
  6. ^ std::tr1::shared_ptr tutorial, anteru.net 
  7. ^ RAII, Dynamic Objects, and Factories in C++ (în engleză), CodeProject,