Sprytne wskaźniki

Trochę tytułem wstępu

Inteligentny wskaźnik pozwala na zarządzanie dynamicznie przydzieloną pamięć zgodnie z wzorcem RAII (Resource Acquisition Is Initialization). Sprytne wskaźniki dostarczają wszystkie interfejsy, które posiadają zwykłe wskaźniki z kilkoma drobnymi wyjątkami. Podczas tworzenia szarządza pamięcią i zwalnia ją, gdy obiekt znajdzie się poza zasięgiem życia. W ten sposób, programista jest zwolniony z ręcznego zarządzania dynamicznie przydzieloną pamięć.

auto_ptr

class Test
{
public:
 Test(int a = 0 ) : m_a(a)
 {
 }
 ~Test( )
 {
  cout<<"Calling destructor"<<endl;
 }
public:
 int m_a;
};
 
int main( )
{
 std::auto_ptr<Test> p( new Test(5) ); 
 cout<<p->m_a<<endl;
}

shared_ptr

int main( )
{
 shared_ptr<int> sptr1( new int );
}

int main( )
{
 shared_ptr<int> sptr1 = make_shared<int>(100);
}

shared_ptr

class Test
{
public:
 Test(int a = 0 ) : m_a(a)
 {
 }
 ~Test( )
 {
  cout<<"Calling destructor"<<endl;
 }
public:
         int m_a;
};

int main( )
{
 shared_ptr<Test> sptr1( new Test[5] );
}

shared_ptr

 

shared_ptr dostarcza operatory *, -> tak jak zwykłe wskaźniki. Oprócz tego dostarcza również takie metody jak:

  • get( ) : aktualny zasób przechowywany przez wskaźnik,
  • reset( ) : zwalnia zasób, jeśli to jest ostatni wskaźnik,
  • unique: pozwala sprawdzić, czy zasób jest wskazywany tylko przez jeden wskaźnik.

shared_ptr

int main( )
{
 shared_ptr<int> sptr1( new int ); // 1
 shared_ptr<int> sptr2 = sptr1; // 2
 shared_ptr<int> sptr3; // 
 sptr3 = sptr2; // 3
}

shared_ptr

int main( )
{
 int* p = new int;
 shared_ptr<int> sptr1( p);
 shared_ptr<int> sptr2( p );
}

shared_ptr

class B;
class A
{
public:
 A(  ) : m_sptrB(nullptr) { };
 ~A( )
 {
  cout<<" A is destroyed"<<endl;
 }
 shared_ptr<B> m_sptrB;
};
class B
{
public:
 B(  ) : m_sptrA(nullptr) { };
 ~B( )
 {
  cout<<" B is destroyed"<<endl;
 }
 shared_ptr<A> m_sptrA;
};

int main( )
{
 shared_ptr<B> sptrB( new B );
 shared_ptr<A> sptrA( new A );
 sptrB->m_sptrA = sptrA;
 sptrA->m_sptrB = sptrB;
}

X2

X3

X1

sp

sp

sp

kontenery z obiektami shared_ptr

A

sp

sp

sp

B

shared_ptr

Błędne działanie, jeśli pamięć bloku jest powiązana z shared_ptrs należącym do innej grupy.

Wszystkie shared_ptrs dzielące tę samą liczbę odwołań należą do grupy.

Kolejny problem gdy tworzymy shared_ptr z nagiego wskaźnika. W powyższym kodzie tylko jeden wspólny wskaźnik jest tworzony przy użyciu p i wtedy kod działa poprawnie.

Jeśli przez pomyłkę, programista zwalnia wskaźnik p przed końcem życia, to jest błąd.

Cykliczna referencja: zasoby nie są zwolnione prawidłowo, jeżeli istnieje cykliczne odniesienie do wspólnych wskaźników

Aby rozwiązać odwołanie cykliczne, C ++ zapewnia inną inteligentną klasę wskaźnika o nazwie weak_ptr

shared_ptr

sp

sp

sp

sp

sp

sp

kontener z obiektami shared_ptr

 

poszczególne obiekty wskazują na siebie nawzajem

weak_ptr

class B;
class A
{
public:
 A(  ) : m_sptrB(nullptr) { };
 ~A( )
 {
  cout<<" A is destroyed"<<endl;
 }
 shared_ptr<B> m_sptrB;
};
class B
{
public:
 B(  ) : m_sptrA(nullptr) { };
 ~B( )
 {
  cout<<" B is destroyed"<<endl;
 }
 shared_ptr<A> m_sptrA;
};

int main( )
{
 shared_ptr<B> sptrB( new B );
 shared_ptr<A> sptrA( new A );
 sptrB->m_sptrA = sptrA;
 sptrA->m_sptrB = sptrB;
}

weak_ptr

sp

sp

sp

sp

sp

sp

kontener z obiektami shared_ptr

 

poszczególne obiekty wskazują na siebie nawzajem poprzez weak_ptr

weak_ptr

A weak pointer pozwala na współdzielenie zasobów, ale sam nie posiada ich. To oznacza, że weak_potr może dzielić zasób przechowywawny przez shared_ptr. Żeby stworzyć weak_ptr, jakiś inny sprytny wskaźnik musi już posiadać zasób, który będzie współdzielony.

sp2

sp1

sp3

wp1

wp2

managed object

Manager Object

wskaźnik:

shared count: 3

weak count: 2

shared_ptrs

weak_ptrs

weak_ptr

weak_ptr nie posiada typowego interfejsu dostarczanego przez wskaźniki, nie ma operatorów * i -> Ponieważ nie posiada wskazywanego zasobu, w ten sposób zabezpiecza przed ewentualnym błędnym użyciem. Jak zatem go używać?

Odpowedź, to stworzenie shared_ptr oprócz weak_ptr i użycie go. Wtedy liczniki ustawią się odpowiednio (co namniej na wartość 1 i nie nastąpi niespodziewane zwolnienie pamięci). Inaczej, zasób trzymany przez weak_ptr, gdy może zostać zwolniony, bo wszystkie shared_ptr, które na niego wskazują go zwolniły.

weak_ptr

int main( )
{
 shared_ptr<Test> sptr( new Test );
 weak_ptr<Test> wptr( sptr );
 weak_ptr<Test> wptr1 = wptr;
}

weak_ptr

Jak sprawdzić, czy zasób jest przechowywany poprawnie?

  1. use_count( ) - zwraca licznik silnych referencji (ile shared_ptr się odwołuje do zasobu).
  2. expired( )  - czy zasób już jest nieaktulny.

weak_ptr

int main( )
{
 shared_ptr<Test> sptr( new Test );
 weak_ptr<Test> wptr( sptr );
 shared_ptr<Test> sptr2 = wptr.lock( );
}

weak_ptr

Stworzenie shared_ptr z weak_ptr zwiększa licznik silnych referencji.

weak_ptr

class B;
class A
{
public:
 A(  ) : m_a(5)  { };
 ~A( ){cout<<" A is destroyed"<<endl;}
 void PrintSpB( );
 weak_ptr<B> m_sptrB;
 int m_a;
};
class B
{
public:
 B(  ) : m_b(10) { };
 ~B( ){cout<<" B is destroyed"<<endl;}
 weak_ptr<A> m_sptrA;
 int m_b;
};

void A::PrintSpB( )
{
 if( !m_sptrB.expired()){  
  cout<< m_sptrB.lock( )->m_b<<endl;
 }
}
int main( ){
 shared_ptr<B> sptrB( new B );
 shared_ptr<A> sptrA( new A );
 sptrB->m_sptrA = sptrA;
 sptrA->m_sptrB = sptrB;
 sptrA->PrintSpB( ); 
}

Unique_ptr

Cechą unique_ptr jest wyłącznosć dostępu do zasobów. W dowolnym momencie, okreslony zasób może być w posiadaniu tylko jednej instancji unique_ptr. Kiedy unique_ptr kończy się czas życia, przechowywany przez niego zasób jest zwalniany. Jeżeli zasób został nadpisany przez inny, to poprzedni jest zwalniany. Wynika z tego, że mamy gwarancję zwolnienia przechowywanego zasobu.

Unique_ptr

#include <memory>

{

std::unique_ptr<int> uptr( new int );

}

unique_ptr jest tworzony w taki sam sposób jak shared_ptr z jednym wyjątkiem. Posiada dodatkowe metody pozwalające na obsługę tablicy obiektów.

 

Unique_ptr

#include <memory>

{
unique_ptr<int[ ]> uptr( new int[5] );

}

Klasa unique_ptr dostarcza specjalizacji dla tablicy elementów, który wywołuje delete[ ] zamiast delete w momencie kiedy kończy się zakres życia instancji sprytnego wskaźnika.

Unique_ptr

Interfejs unique_ptr jest bardzo podobny do obsługi zwykłego wskaźnika za wyjątkiem operacji arytmetycznych, które nie są dozwolone.

unique_ptr dostarcza funkcji release(), która rezygnuje z prawa własności do zasobu. Różnica między release() i reset( ) polega na tym, że release nie wywołuje delete i nie niszczy przydzielonego zasobu, natomiast reset już tak.

sprytne wskaźniki

By pedzimaz

sprytne wskaźniki

Omówienie podstawowych klas sprytnych wskaźników z biblioteki standardowej: shared_ptr, weak_ptr, unique_ptr.

  • 861