La differenza è che std::make_shared
esegue un'allocazione di heap, mentre chiamare il std::shared_ptr
costruttore ne esegue due.
Dove avvengono le allocazioni di heap?
std::shared_ptr
gestisce due entità:
- il blocco di controllo (memorizza metadati come ref-count, deleter cancellati dal tipo, ecc.)
- l'oggetto da gestire
std::make_shared
esegue una singola contabilità di allocazione dell'heap per lo spazio necessario sia per il blocco di controllo che per i dati. Nell'altro caso, new Obj("foo")
invoca un'allocazione di heap per i dati gestiti e il std::shared_ptr
costruttore ne esegue un altro per il blocco di controllo.
Per ulteriori informazioni, consultare le note di implementazione su cppreference .
Aggiornamento I: Eccezione-Sicurezza
NOTA (2019/08/30) : questo non è un problema dal C ++ 17, a causa delle modifiche nell'ordine di valutazione degli argomenti delle funzioni. In particolare, ogni argomento di una funzione deve essere eseguito completamente prima della valutazione di altri argomenti.
Dato che l'OP sembra interrogarsi sul lato della sicurezza delle eccezioni, ho aggiornato la mia risposta.
Considera questo esempio,
void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }
F(std::shared_ptr<Lhs>(new Lhs("foo")),
std::shared_ptr<Rhs>(new Rhs("bar")));
Poiché C ++ consente un ordine arbitrario di valutazione delle sottoespressioni, un possibile ordinamento è:
new Lhs("foo"))
new Rhs("bar"))
std::shared_ptr<Lhs>
std::shared_ptr<Rhs>
Supponiamo ora che venga generata un'eccezione al passaggio 2 (ad esempio, eccezione di memoria insufficiente, il Rhs
costruttore ha generato un'eccezione). Perdiamo quindi la memoria allocata al passaggio 1, poiché nulla avrà avuto la possibilità di ripulirla. Il nocciolo del problema qui è che il puntatore non elaborato non è stato passato std::shared_ptr
immediatamente al costruttore.
Un modo per risolvere questo problema è eseguirli su linee separate in modo che questo ordinamento arbitrale non possa avvenire.
auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);
Il modo preferito per risolvere questo ovviamente è usare std::make_shared
invece.
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));
Aggiornamento II: svantaggio di std::make_shared
Citando i commenti di Casey :
Poiché esiste una sola allocazione, la memoria del puntatore non può essere deallocata fino a quando il blocco di controllo non è più in uso. A weak_ptr
può mantenere il blocco di controllo in vita indefinitamente.
Perché le istanze di weak_ptr
s mantengono attivo il blocco di controllo?
Deve esserci un modo per weak_ptr
s per determinare se l'oggetto gestito è ancora valido (es. Per lock
). Lo fanno controllando il numero di shared_ptr
s che possiedono l'oggetto gestito, che è memorizzato nel blocco di controllo. Il risultato è che i blocchi di controllo sono attivi fino a quando il shared_ptr
conteggio e il weak_ptr
conteggio non raggiungono entrambi 0.
Torna a std::make_shared
Poiché std::make_shared
effettua una singola allocazione di heap sia per il blocco di controllo che per l'oggetto gestito, non è possibile liberare la memoria per il blocco di controllo e l'oggetto gestito in modo indipendente. Dobbiamo aspettare fino possiamo liberare sia il blocco di controllo e l'oggetto gestito, che risulta essere fino a quando non ci sono shared_ptr
s o weak_ptr
è vivo.
Supponiamo che abbiamo invece eseguito due allocazioni di heap per il blocco di controllo e l'oggetto gestito tramite new
e shared_ptr
costruttore. Quindi liberiamo la memoria per l'oggetto gestito (forse prima) quando non ci sono shared_ptr
s in vita, e liberiamo la memoria per il blocco di controllo (forse più tardi) quando non ci sono weak_ptr
s in vita.