Decidere quale puntatore intelligente utilizzare è una questione di proprietà . Quando si tratta di gestione delle risorse, l'oggetto A possiede l' oggetto B se controlla la durata dell'oggetto B. Ad esempio, le variabili membro sono di proprietà dei rispettivi oggetti perché la durata delle variabili membro è legata alla durata dell'oggetto. Scegli i puntatori intelligenti in base alla proprietà dell'oggetto.
Si noti che la proprietà in un sistema software è separata dalla proprietà come la penseremmo al di fuori del software. Ad esempio, una persona potrebbe "possedere" la propria casa, ma ciò non significa necessariamente che un Person
oggetto abbia il controllo sulla durata di un House
oggetto. Confondere questi concetti del mondo reale con i concetti del software è un modo sicuro per programmare te stesso in un buco.
Se si possiede la proprietà esclusiva dell'oggetto, utilizzare std::unique_ptr<T>
.
Se hai la proprietà condivisa dell'oggetto ...
- Se non ci sono cicli nella proprietà, usa std::shared_ptr<T>
.
- Se ci sono cicli, definire una "direzione" e utilizzare std::shared_ptr<T>
in una direzione e std::weak_ptr<T>
nell'altra.
Se l'oggetto ti possiede, ma esiste il potenziale di non avere proprietario, usa i normali puntatori T*
(ad esempio i puntatori principali).
Se l'oggetto ti possiede (o comunque ha un'esistenza garantita), usa i riferimenti T&
.
Avvertenza: prestare attenzione ai costi dei puntatori intelligenti. In ambienti con memoria o prestazioni limitate, potrebbe essere utile utilizzare solo normali puntatori con uno schema più manuale per la gestione della memoria.
I costi:
- Se si dispone di un deleter personalizzato (ad es. Si utilizzano pool di allocazione), ciò comporterà un sovraccarico per puntatore che può essere facilmente evitato con l'eliminazione manuale.
std::shared_ptr
ha l'overhead di un incremento del conteggio dei riferimenti sulla copia, oltre a un decremento sulla distruzione seguito da un controllo del conteggio 0 con cancellazione dell'oggetto trattenuto. A seconda dell'implementazione, questo può gonfiare il codice e causare problemi di prestazioni.
- Tempo di compilazione. Come per tutti i modelli, i puntatori intelligenti contribuiscono negativamente alla compilazione dei tempi.
Esempi:
struct BinaryTree
{
Tree* m_parent;
std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};
Un albero binario non possiede il suo genitore, ma l'esistenza di un albero implica l'esistenza del suo genitore (o nullptr
come root), quindi utilizza un normale puntatore. Un albero binario (con semantica di valore) ha la proprietà esclusiva dei suoi figli, quindi quelli lo sono std::unique_ptr
.
struct ListNode
{
std::shared_ptr<ListNode> m_next;
std::weak_ptr<ListNode> m_prev;
};
Qui, il nodo elenco possiede i suoi elenchi successivo e precedente, quindi definiamo una direzione e utilizziamo shared_ptr
per next e weak_ptr
for prev per interrompere il ciclo.