Tracce
/
C++
C++
/
Programma
/
Smart pointer
Sm

Smart pointer in C++

1 esercizio

Informazioni su Smart pointer

I puntatori intelligenti sono una funzionalità moderna del C++, pensata per offrire una gestione automatica della memoria e per aiutare a prevenire le perdite di memoria e i puntatori pendenti che si associano comunemente ai puntatori grezzi. Fungono da wrapper attorno ai puntatori grezzi, aggiungendo funzionalità come la deallocazione automatica della memoria quando il puntatore non serve più.

Sintassi generale

I puntatori intelligenti sono in genere implementati come template di classe nella libreria standard del C++. I due puntatori intelligenti più usati sono std::unique_ptr e std::shared_ptr.

Puntatori unici

std::unique_ptr è un puntatore intelligente che possiede l'oggetto in modo esclusivo. Garantisce che in qualsiasi momento un solo oggetto std::unique_ptr possieda la risorsa. Quando il std::unique_ptr che la possiede viene distrutto o resettato, distrugge automaticamente gli oggetti e ne rilascia la memoria.

#include <memory>
// Declaring and defining a unique pointer
auto rightful_king_of_england = std::make_unique<std::string>("Excalibur");

// Unique pointers cannot be copied or assigned
auto mordred = rightful_king_of_england; // Error: Cannot copy a unique_ptr

Vantaggi di std::make_unique()

Quando si crea uno std::unique_ptr, è preferibile usare std::make_unique() invece di allocare la memoria direttamente con new. std::make_unique() offre diversi vantaggi:

  1. Sicurezza rispetto alle eccezioni: std::make_unique() garantisce la sicurezza rispetto alle eccezioni. Se viene lanciata un'eccezione durante la costruzione dell'oggetto, la memoria viene deallocata automaticamente, prevenendo perdite di memoria.
  2. Chiarezza: usare std::make_unique() rende il codice più chiaro e conciso. Elimina la necessità di specificare esplicitamente il tipo da allocare, poiché gli argomenti del template vengono dedotti automaticamente.
  3. Opportunità di ottimizzazione: i compilatori hanno l'opportunità di ottimizzare std::make_unique() in modo più efficace rispetto all'allocazione manuale della memoria con new, con potenziali miglioramenti delle prestazioni.
  4. Evitare un uso improprio: è possibile eliminare la risorsa sottostante quando lo std::unique_ptr viene costruito manualmente. Questo porterebbe a un comportamento indefinito, quando lo std::unique_ptr prova a eliminarla alla fine dello scope.

Puntatori condivisi

std::shared_ptr è un puntatore intelligente che permette a più oggetti std::shared_ptr di condividere la proprietà della stessa risorsa. Tiene traccia di quanti puntatori condivisi fanno riferimento alla risorsa e dealloca la memoria solo quando l'ultimo puntatore condiviso che la possiede esce dallo scope o viene resettato.

// Declaring and defining a shared pointer to a dynamically allocated string
auto martian_congressional_republic = std::make_shared<std::string>("protomolecule");

// Creating more shared pointer that shares ownership
auto outer_planets_alliance = martian_congressional_republic;
auto united_nations = martian_congressional_republic;
Caution

In C++17 e versioni precedenti, usare std::shared_ptr con gli array tramite std::make_shared<T[]> non è supportato direttamente. Sebbene sia possibile allocare array con std::make_shared<T[]>, creare puntatori condivisi direttamente da essi può portare a un comportamento indefinito, a causa delle differenze nella gestione della memoria tra oggetti singoli e array. Valuta invece di usare std::vector o funzioni di eliminazione personalizzate per gestire gli array con i puntatori condivisi in modo efficace. Assicurati sempre della compatibilità con il tuo compilatore e con la tua implementazione della libreria standard quando hai a che fare con allocazioni di array e puntatori condivisi in C++17.

Vantaggi di std::make_shared()

Simile a std::make_unique(), std::make_shared() offre vantaggi come una migliore efficienza della memoria, la sicurezza rispetto alle eccezioni e la leggibilità. Combina l'allocazione della memoria per il blocco di controllo e per l'oggetto gestito in un'unica operazione, migliorando l'efficienza e riducendo il rischio di perdite di memoria. Inoltre, la deduzione automatica degli argomenti del template semplifica il codice e ne migliora la leggibilità. Usare std::make_shared() favorisce un codice più pulito, sicuro ed efficiente quando si lavora con oggetti std::shared_ptr in C++.

Puntatori deboli

std::weak_ptr è una classe complementare a std::shared_ptr che fornisce un riferimento «debole» non proprietario a un oggetto gestito da un puntatore condiviso. Permette di accedere alla risorsa senza influire sulla sua durata. I puntatori deboli sono utili negli scenari in cui è necessario interrompere i riferimenti ciclici per prevenire perdite di memoria.

// Creating a shared pointer
auto your_account = std::make_shared<std::string>("secret_subscription_password");
// Creating a shared pointer that shares ownership
auto your_flatmates_account = your_account;

// Creating a weak pointer from the shared pointer
auto your_flatmates_boyfriends_account = your_flatmates_account;
// if your_account and your_flatmates_account are deleted, there is no more reference to the shared pointer.
// your_flatmates_boyfriends_account will be a null pointer and cannot use the associated object any longer.

std::weak_ptr e la proprietà ciclica

std::weak_ptr è stato progettato per risolvere il problema della proprietà ciclica, nota anche come riferimenti circolari, che può verificarsi usando std::shared_ptr.

In uno scenario di proprietà ciclica, due o più oggetti std::shared_ptr si riferiscono l'uno all'altro, creando un ciclo in cui nessuno degli oggetti può essere eliminato, perché hanno riferimenti forti reciproci, il che porta a perdite di memoria.

std::weak_ptr offre una soluzione a questo problema, permettendo riferimenti deboli a oggetti condivisi senza contribuire al loro conteggio dei riferimenti. Ciò significa che può osservare e accedere all'oggetto condiviso, ma non ne impedisce l'eliminazione. Se tutti i riferimenti forti all'oggetto condiviso vengono rilasciati, l'oggetto viene distrutto e i puntatori deboli che lo osservano vengono automaticamente resettati a nullptr.

Ecco un breve esempio che mostra l'uso di std::weak_ptr per interrompere una proprietà ciclica:

struct Node {
    Node() { std::cout << "Node constructed\n"; }
    ~Node() { std::cout << "Node destructed\n"; }

    std::weak_ptr<Node> next;
};

// Creating two nodes
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();

// Creating a cycle by making each node point to the other
node1->next = node2;
node2->next = node1;

// Release strong references to nodes
node1.reset();
node2.reset();

// Nodes are automatically destructed due to weak references

"Node constructed" viene stampato due volte, seguito da due righe di "Node destructed".

Quando next non è un puntatore debole ma uno std::shared_ptr, si crea una situazione di proprietà ciclica che porta a perdite di memoria, perché entrambi i nodi manterranno riferimenti forti reciproci, impedendone la distruzione.

struct Node {
    Node() { std::cout << "Node constructed\n"; }
    ~Node() { std::cout << "Node destructed\n"; }

    std::shared_ptr<Node> next; // Change to shared_ptr
};

In questo scenario, sia node1 che node2 mantengono riferimenti forti reciproci tramite std::shared_ptr, formando un ciclo. Quando il programma termina e tenta di rilasciare i riferimenti forti con reset(), i conteggi dei riferimenti di entrambi i nodi restano diversi da zero, perché ogni nodo mantiene un riferimento forte all'altro. Di conseguenza, i nodi non vengono distrutti, il che porta a perdite di memoria. "Node constructed" viene stampato due volte, ma non è seguito da alcuna riga di "Node destructed".

Puntatori pendenti e std::weak_ptr

I puntatori pendenti si verificano quando un puntatore fa riferimento a un oggetto che è stato eliminato, portando a un comportamento indefinito quando lo si dereferenzia. std::weak_ptr aiuta a evitare i puntatori pendenti fornendo un riferimento debole e non proprietario a un oggetto gestito da std::shared_ptr. Ecco come evitare i puntatori pendenti con std::weak_ptr:

  1. Crea uno std::weak_ptr: invece di mantenere direttamente uno std::shared_ptr, crea uno std::weak_ptr verso l'oggetto condiviso.

  2. Verifica la validità: prima di usare lo std::weak_ptr, verifica la sua validità con la funzione expired(). Questa funzione restituisce true se l'oggetto condiviso associato è stato eliminato.

  3. Blocca lo std::weak_ptr: per accedere all'oggetto condiviso in sicurezza, usa la funzione lock(), che restituisce uno std::shared_ptr che punta allo stesso oggetto, se è ancora valido. Se l'oggetto è stato eliminato, lock() restituisce uno std::shared_ptr vuoto.

Ecco un esempio di codice che mostra l'uso di std::weak_ptr per evitare i puntatori pendenti:

auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();

// Create weak pointers
node1->next = node2;
node2->next = node1;

// Check validity and lock
if (auto lockedNode2 = node1->next.lock()) {
    // Use lockedNode2 safely
} else {
    // Handle case where node2 has been deleted
}

In questo esempio, node1->next e node2->next sono oggetti std::weak_ptr. Prima di accedere all'oggetto condiviso, se ne verifica la validità con la funzione expired(). Poi si usa lock() per accedere all'oggetto condiviso in sicurezza. Questo approccio garantisce che i puntatori pendenti vengano evitati quando si lavora con std::weak_ptr.

Consigli d'uso

Usa i puntatori intelligenti come impostazione predefinita: std::unique_ptr per la proprietà esclusiva e std::shared_ptr per la proprietà condivisa. Riserva i puntatori grezzi ai riferimenti non proprietari o all'interfacciamento con codice legacy. Nella maggior parte dei casi, std::unique_ptr è sufficiente per la proprietà esclusiva, perché offre una gestione della memoria leggera, senza il sovraccarico del conteggio dei riferimenti. std::shared_ptr va usato con parsimonia, perché introduce sovraccarico e complessità, a meno che non serva una vera proprietà condivisa. std::weak_ptr è specializzato per interrompere dipendenze cicliche o per osservare oggetti condivisi, ma non è di uso comune.

Modifica tramite GitHub Il collegamento si apre in una nuova finestra o scheda

Impara Smart pointer