Un puntatore "grezzo" è non gestito. Cioè, la seguente riga:
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
... perderà memoria se una delete
di accompagnamento non viene eseguita al momento opportuno.
auto_ptr
Per ridurre al minimo questi casi, è stato introdotto std::auto_ptr<>
. A causa delle limitazioni del C ++ precedenti allo standard 2011, tuttavia, è ancora molto facile che auto_ptr
elimini memoria. È sufficiente per casi limitati, come questo, tuttavia:
void func() {
std::auto_ptr<SomeKindOfObject> sKOO_ptr(new SomeKindOfObject());
// do some work
// will not leak if you do not copy sKOO_ptr.
}
Uno dei casi d'uso più deboli è nei container. Questo perché se viene eseguita una copia di auto_ptr<>
e la vecchia copia non viene reimpostata con attenzione, il contenitore può eliminare il puntatore e perdere i dati.
unique_ptr
In sostituzione, C ++ 11 ha introdotto std::unique_ptr<>
:
void func2() {
std::unique_ptr<SomeKindofObject> sKOO_unique(new SomeKindOfObject());
func3(sKOO_unique); // now func3() owns the pointer and sKOO_unique is no longer valid
}
Tale unique_ptr<>
verrà ripulito correttamente, anche quando è passato tra le funzioni. Lo fa rappresentando semanticamente la "proprietà" del puntatore - il "proprietario" lo ripulisce. Questo lo rende ideale per l'uso in container:
std::vector<std::unique_ptr<SomeKindofObject>> sKOO_vector();
Diversamente da auto_ptr<>
, unique_ptr<>
si comporta bene qui e quando vector
ridimensiona, nessuno degli oggetti verrà eliminato per errore mentre vector
copia il suo backing store.
shared_ptr
e weak_ptr
unique_ptr<>
è utile, per sicurezza, ma ci sono casi in cui vuoi che due parti della tua base di codice siano in grado di riferirsi allo stesso oggetto e copiare il puntatore, pur essendo comunque garantita la corretta pulizia. Ad esempio, un albero potrebbe apparire come questo, quando si utilizza std::shared_ptr<>
:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
In questo caso, possiamo persino conservare più copie di un nodo radice e l'albero verrà pulito in modo corretto quando tutte le copie del nodo radice verranno distrutte.
Funziona perché ogni shared_ptr<>
trattiene non solo il puntatore all'oggetto, ma anche un conteggio di riferimento di tutti gli oggetti shared_ptr<>
che si riferiscono allo stesso puntatore. Quando viene creato uno nuovo, il conteggio aumenta. Quando uno viene distrutto, il conteggio diminuisce. Quando il conteggio raggiunge lo zero, il puntatore è delete
d.
Quindi questo introduce un problema: le strutture a collegamento doppio finiscono con riferimenti circolari. Supponiamo di voler aggiungere un puntatore parent
al nostro albero Node
:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Ora, se rimuoviamo un Node
, c'è un riferimento ciclico ad esso. Non sarà mai delete
d perché il suo conteggio dei riferimenti non sarà mai pari a zero.
Per risolvere questo problema, usi un std::weak_ptr<>
:
template<class T>
struct Node {
T value;
std::weak_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Ora, le cose funzioneranno correttamente e la rimozione di un nodo non lascerà riferimenti bloccati al nodo genitore. Rende l'albero un po 'più complicato, tuttavia:
std::shared_ptr<Node<T>> parent_of_this = node->parent.lock();
In questo modo, puoi bloccare un riferimento al nodo e hai una ragionevole garanzia che non scomparirà mentre ci stai lavorando, dal momento che stai mantenendo un shared_ptr<>
di esso.
make_shared
e make_unique
Ora, ci sono alcuni problemi minori con shared_ptr<>
e unique_ptr<>
che dovrebbero essere indirizzati. Le seguenti due righe hanno un problema:
foo_unique(std::unique_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
foo_shared(std::shared_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
Se thrower()
genera un'eccezione, entrambe le linee perdono memoria. Inoltre, shared_ptr<>
mantiene il conteggio dei riferimenti lontano dall'oggetto a cui punta e questo può significa una seconda allocazione). Di solito non è consigliabile.
C ++ 11 fornisce std::make_shared<>()
e C ++ 14 fornisce std::make_unique<>()
per risolvere questo problema:
foo_unique(std::make_unique<SomeKindofObject>(), thrower());
foo_shared(std::make_shared<SomeKindofObject>(), thrower());
Ora, in entrambi i casi, anche se thrower()
genera un'eccezione, non ci sarà una perdita di memoria. Come bonus, make_shared<>()
ha l'opportunità di creare il suo conteggio di riferimento nello stesso spazio di memoria come oggetto gestito, che può essere sia più veloce sia in grado di salvare pochi byte di memoria, mentre ti dà un garanzia di sicurezza eccezionale!
Note su Qt
Va notato, tuttavia, che Qt, che deve supportare i compilatori pre-C ++ 11, ha il proprio modello di garbage collection: molti QObject
s hanno un meccanismo in cui verranno distrutti correttamente senza la necessità di l'utente a delete
di loro.
Non so come si comporterà QObject
s quando gestito da puntatori gestiti C ++ 11, quindi non posso dire che shared_ptr<QDialog>
sia una buona idea. Non ho abbastanza esperienza con Qt per dirlo con certezza, ma io credo che Qt5 sia stato adattato per questo caso d'uso.