Un puntatore "grezzo" non è gestito. Cioè, la seguente riga:
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
... perderà memoria se un accompagnamento delete
non viene eseguito al momento opportuno.
auto_ptr
Al fine di minimizzare questi casi, è std::auto_ptr<>
stato introdotto. A causa delle limitazioni di C ++ precedenti allo standard del 2011, tuttavia, è ancora molto facile auto_ptr
perdere la 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 suoi casi d'uso più deboli è nei container. Questo perché se auto_ptr<>
viene creata una copia di una copia precedente e la copia precedente non viene accuratamente ripristinata, il contenitore potrebbe 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<>
sarà ripulito correttamente, anche quando viene passato tra le funzioni. Lo fa rappresentando semanticamente la "proprietà" del puntatore: il "proprietario" lo pulisce. Questo lo rende ideale per l'uso in contenitori:
std::vector<std::unique_ptr<SomeKindofObject>> sKOO_vector();
Diversamente auto_ptr<>
, qui unique_ptr<>
è ben educato e quando i vector
ridimensionamenti, nessuno degli oggetti verrà accidentalmente cancellato mentre le vector
copie sono archiviate.
shared_ptr
e weak_ptr
unique_ptr<>
è utile, certo, ma ci sono casi in cui si desidera che due parti della base di codice siano in grado di fare riferimento allo stesso oggetto e copiare il puntatore in giro, pur garantendo comunque una corretta pulizia. Ad esempio, un albero potrebbe assomigliare a 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à ripulito correttamente quando tutte le copie del nodo radice vengono distrutte.
Questo funziona perché ognuno di essi shared_ptr<>
tiene conto non solo del puntatore all'oggetto, ma anche di un conteggio di riferimento di tutti gli shared_ptr<>
oggetti che si riferiscono allo stesso puntatore. Quando ne 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 doppio collegamento finiscono con riferimenti circolari. Supponiamo di voler aggiungere un parent
puntatore 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 di riferimento non sarà mai zero.
Per risolvere questo problema, si utilizza 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 padre. Rende il camminare sull'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, dato che ti stai aggrappando a uno shared_ptr<>
di essi.
make_shared
e make_unique
Ora, ci sono alcuni problemi minori con shared_ptr<>
e unique_ptr<>
che dovrebbero essere affrontati. 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 perderanno memoria. E più di questo, shared_ptr<>
tiene il conteggio dei riferimenti lontano dall'oggetto a cui punta e questo può significare una seconda allocazione). Di solito non è desiderabile.
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 la possibilità di creare il suo conteggio di riferimento nello stesso spazio di memoria dell'oggetto gestito, che può essere sia più veloce che in grado di risparmiare qualche byte di memoria, offrendo una 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
hanno un meccanismo in cui verranno distrutti correttamente senza che l'utente debbadelete
.
Non so come QObject
si comporteranno se gestiti 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 credo che Qt5 sia stato adattato per questo caso d'uso.