C++ Puntatori

Da Andreabont's Wiki.

Puntatori classici

Questi sono i puntatori ereditati dal C, sono il modo più semplice e di "basso livello" disponibile, e per questo anche il più pericoloso.

int* p_var = nullptr;
p_var = new int(5);
std::cout << p_var << std::endl; // Stampo indirizzo dove posso trovare 5
std::cout << *p_var << std::endl; // Stampo 5
delete p_var;

Nell'esempio vediamo la dichiarazione di p_var, un puntatore ad un numero intero, in questo caso il valore di p_var coincide con l'indirizzo di memoria dove trovare il valore dell'intero voluto. Nella dichiarazione viene settato a null.
Dopo possiamo allocare lo spazio per un intero e lo inizializziamo al valore "5", salvando l'indirizzo di memoria della zona allocata in p_var.
Finito l'uso è necessario deallocare la zona di memoria, altrimenti si rischiano memory leak (la deallocazione automatica avviene al termine del processo)

Reference

Il tipo reference è un puntatore che viene semplificato e reso più sicuro rendendo impossibili determinati usi possibilmente pericolosi.

int var = 5;
std::cout << var << std::endl; // Stampo 5
int& r_var = var;
std::cout << r_var << std::endl; // Stampo 5

Il comportamento di basso livello è identico ai puntatori classici, r_var è l'indirizzo di memoria di var. Ma la differenza sta nell'uso che chi programma può fare di una reference:

  • Una reference non può essere nulla, deve sempre puntare ad una variabile esistente.
  • Una reference non può essere usata nella aritmetica dei puntatori

Ad alto livello è possibile usare indistintamente var e r_var come se fossero la stessa variabile (e di fatto lo sono dato che fanno riferimento alla stessa zona di memoria), possiamo quindi dire che una reference è un alias di un'altra variabile.

Puntatori condivisi (std::shared_ptr)

Questi puntatori sono virtuali, e vengono forniti dalla libreria standard. L'idea alla base è automatizzare la deallocazione della memoria quando nessuno la utilizza più, implementando un semplicissimo garbage collector deterministico.

std::shared_ptr<int> sptr1( new int(5) );

A livello di programmazione sptr1 si comporta come se fosse un puntatore classico, con la differenza che non c'è più bisogno di pensare alla deallocazione della memoria.
Il puntatore condiviso tiene in memoria quanti stanno usando quella zona di memoria. Appena viene distrutto l'ultimo puntatore si preoccupa di deallocare la memoria.

Possiamo usare anche la versione "automatica" (e più veloce):

auto sptr1 = std::make_shared<int>(5);

Nota: Implementando internamente il conteggio dei riferimenti alla zona di memoria il passaggio per parametri di uno shared pointer può avvenire per copia (senza necessità di passare una reference)

Se il nostro shared_ptr sta mantenendo un array di puntatori, allora è necessario fornigli un metodo custom per chiamare il distruttore correttamente:

std::shared_ptr<int> intArray(new int[3], [](int* p) {delete[] p; });

Puntatori univoci (std::unique_ptr)

Arrow right blue.pngVedi anche la pagina: C++ Unique Pointer.

Sono puntatori virtuali, anche questi forniti dalla libreria standard. L'idea è quella di legare il puntatore allo scope, e deallocare la memoria automaticamente all'uscita dello scope.

{
   std::unique_ptr<int> uptr1( new int(5) );
   
   // Altro codice che usa uptr1...

} // Deallocazione della memoria puntata da uptr1

Anche questo si comporta esattamente come se fosse un puntatore classico. Per semplicità possiamo usare una versione "automatica", e più veloce (C++14):

{
   auto uptr1 = std::make_unique<int>(5);

  // Altro codice che usa uptr1...

}// Deallocazione della memoria puntata da uptr1