ProgrammazioneSviluppatore C++ Middle

Spiega le differenze tra un normale puntatore e un puntatore intelligente (ad esempio, std::unique_ptr) in C++. Perché i puntatori intelligenti rendono i programmi più affidabili?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

Storia della questione:

I puntatori normali (raw) sono il meccanismo tradizionale in C++ per lavorare con la memoria dinamica. Questo è un meccanismo astratto universale, ma sfortunatamente è molto soggetto a errori: perdite di memoria, doppie eliminazioni, errori di Dangling pointer. Perciò, a partire da C++11, la libreria standard include puntatori intelligenti — classi template (std::unique_ptr, std::shared_ptr, std::weak_ptr) che gestiscono automaticamente il tempo di vita dell'oggetto.

Problema:

Quando si utilizzano puntatori normali, la responsabilità per l'allocazione e la liberazione della memoria ricade sul programmatore. Errori nella liberazione della memoria portano a perdite di memoria (memory leaks), corruzione dei dati, crash del programma. Casi particolarmente complessi si verificano nella gestione delle eccezioni o quando i puntatori vengono passati ad altre funzioni.

Soluzione:

I puntatori intelligenti incapsulano la memoria allocata e la liberano automaticamente quando non ci sono più proprietari. Implementano RAII. std::unique_ptr fornisce un possesso esclusivo, std::shared_ptr — possesso condiviso, std::weak_ptr — non controllante (per prevenire "cicli di riferimento").

Esempio di codice:

#include <memory> void foo() { std::unique_ptr<int> p = std::make_unique<int>(5); // la memoria verrà liberata anche in caso di eccezione // ... }

Caratteristiche chiave:

  • I puntatori intelligenti liberano le risorse automaticamente alla distruzione
  • std::unique_ptr vieta la copia, solo la semantica di spostamento (move)
  • std::shared_ptr implementa "conteggio dei riferimenti"

Domande insidiose.

Si può usare std::unique_ptr su un array creato tramite new[]?

No, per gli array usa std::unique_ptr<T[]>: in questo modo verrà chiamato delete[] invece di delete.

std::unique_ptr<int[]> arr(new int[10]);

I puntatori intelligenti standard possono prevenire tutte le possibili perdite di memoria?

No. Ad esempio, i riferimenti ciclici tra std::shared_ptr porteranno a perdite. Per rompere tali cicli si usa std::weak_ptr.

Possono i puntatori intelligenti "eliminare" lo stesso oggetto due volte?

No, a meno che non si violi la logica di possesso (non usare puntatori normali!). Se si copia manualmente un puntatore normale, allora la distruzione potrebbe avvenire due volte.

Errori tipici e anti-patterns

  • Copiare std::unique_ptr porterà a un errore di compilazione
  • Usare contemporaneamente un puntatore intelligente e un puntatore normale sullo stesso oggetto
  • Non usare std::weak_ptr per risolvere le dipendenze cicliche

Esempio della vita reale

Caso negativo

Si usano puntatori normali, la memoria viene liberata manualmente, in caso di eccezione la memoria non viene liberata.

Pro:

  • Chiaro in compiti semplici

Contro:

  • Perdite di memoria regolari
  • Potenziale doppia eliminazione
  • Difficile mantenere il codice

Caso positivo

Si usano std::unique_ptr e std::make_unique per creare oggetti e passarli a funzioni.

Pro:

  • Praticamente impossibile avere perdite di memoria
  • Interfaccia RAII, semplice possesso e passaggio di oggetti

Contro:

  • Richiede comprensione della semantica di spostamento e del funzionamento della libreria standard