08
Iul
2008

Pana la TR1 singurul smart pointer dinsponibil in biblioteca standard era auto_ptr, care datorita modelului de posesie exclusiva (exclusive ownership) pe care este construit are cateva probleme majore. Pentru a rezolva aceste lucruri, noul standard ofera doua noi implementari, shared_ptr si weak_ptr, amandoua definite in headerul <memory>.

Pointerul std::auto_ptr

Dupa cum spuneam mai sus, auto_ptr se bazeaza pe posesia exlusiva a unei resurse, cea ce inseamna ca doi pointeri (de acest tip) nu pot indica spre aceiasi resursa. Copierea sau atribuirea fac ca resursa sa intre in posesia pointerul destinatie.

#include <memory>
#include <iostream>

class foo 
{
public:
   void print() {std::cout << "foo::print" << std::endl;}
};

int main()
{
   std::auto_ptr<foo> ap1(new foo);
   ap1->print();
   std::cout << "ap1 pointer: " << ap1.get() << std::endl;

   std::auto_ptr ap2(ap1);
   ap2->print();
   std::cout << "ap1 pointer: " << ap1.get() << std::endl;
   std::cout << "ap2 pointer: " << ap2.get() << std::endl;
   
   return 0;
}

Acest program afiseaza

foo::print
ap1 pointer: 0033A790
foo::print
ap1 pointer: 00000000
ap2 pointer: 0033A790

Valoarea exacta a pointerului incapsulat e mai putin importanta. Ce e important, e ca in urma construirii lui ap2, obietul ap1 a cedat posesia resursei, si pointerul pe care il incapsula a devenit NULL.

Probleme majore create de auto_ptr sunt:

  • copierea si atribuirea schimba posesorul unui obiect, modificat nu doar destinatia ci si sursa, ceea ce nu este intuitiv;
  • nu se poate folosi in containere STL, intrucat restrictia pentru elementele acestor containere e sa fie construibile prin copiere si asignabile, iar auto_ptr nu indeplineste aceste conditii, din moditivul explicat mai sus;

Noutati in TR1

Doua noi implementari de smart pointeri au fost introduse in TR1. Acestea sunt:

  • sharted_ptr: bazat pe un contor de referinte, care se incrementeaza de fiecare data un nou pointer pointeaza spre resursa, si se decrementeaza de fiecare data cand destructorul este apelat; cand contorul ajunge la 0 resursa este eliberata. Acest pointer este construibil prin copiere si asignabil, prin urmare poate fi folosit cu containere STL. Pe langa acestea un shared_ptr poate lucra cu tipuri polimorfice, dar si tipuri incomplete. Problema este ca nu poate evita dependintele circulare, caz in care resursele nu mai ajung sa fie eliberate (ex: un arbore in care un nod are referinte la copii, dar si la parinte, caz in care parintele si copiii se refera reciproc). Pentru rezolvarea acestei probleme a fost introdus:
  • weak_ptr: pointeaza spre o resursa referita de un shared_ptr, fara a afecta insa contorul de referinte al acestui obiect. Cand contorul ajunge la zero, resursa referita de un sharted_ptr este eliberata, indiferent cati weak_ptr o mai refera.

Exemplul de mai jos reia cazul anterior, inlocuind auto_ptr cu shared_ptr:

int main()
{
   std::tr1::shared_ptr<foo> sp1(new foo);
   sp1->print();
   std::cout << "sp1 pointer: " << sp1.get() << std::endl;

   std::tr1::shared_ptr<foo> sp2(sp1);
   sp2->print();
   std::cout << "sp1 pointer: " << sp1.get() << std::endl;
   std::cout << "sp2 pointer: " << sp2.get() << std::endl;

   std::cout << "contor sp1: " << sp1.use_count() << std::endl;
   std::cout << "contor sp2: " << sp2.use_count() << std::endl;
   
   return 0;
}
is foo::print
sp1 pointer: 0033A790
foo::print
sp1 pointer: 0033A790
sp2 pointer: 0033A790
contor sp1: 2
contor sp2: 2

Dupa cum se observa, atunci cand sp2 este construit, sp1 nu cedeaza posesia, modificandu-si pointerul intern la NULL, ci doar se incrementeaza contorul de referinte. Cand cele doua obiecte vor iesi din scope, ultimul care va fi distrus va elibera resursa.