Differenze tra std :: make_unique e std :: unique_ptr con new


130

Ha std::make_uniquedei vantaggi in termini di efficienza come std::make_shared?

Rispetto alla costruzione manuale std::unique_ptr:

std::make_unique<int>(1);         // vs
std::unique_ptr<int>(new int(1));

Ha make_sharedqualche efficacia oltre a scrivere il codice a mano lunga?
Ed Heal,

9
@EdHeal Potrebbe, perché make_sharedpuò allocare sia lo spazio per l'oggetto che lo spazio per il blocco di controllo insieme in una singola allocazione. Il costo di ciò è che l'oggetto non può essere deallocato separatamente dal blocco di controllo, quindi se usi weak_ptrmolto puoi finire per usare più memoria.
bames53

Forse questo è un buon punto di partenza stackoverflow.com/questions/9302296/…
Ed Heal

Risposte:


140

La motivazione dietro make_uniqueè principalmente duplice:

  • make_uniqueè sicuro per la creazione di provvisori, mentre con l'uso esplicito di newte devi ricordare la regola di non usare provvisori senza nome.

    foo(make_unique<T>(), make_unique<U>()); // exception safe
    
    foo(unique_ptr<T>(new T()), unique_ptr<U>(new U())); // unsafe*
  • L'aggiunta di make_uniquefinalmente significa che possiamo dire alle persone di non "mai" usare newpiuttosto che la regola precedente di "non usare mai" newtranne quando si fa un unique_ptr".

C'è anche un terzo motivo:

  • make_uniquenon richiede l'utilizzo del tipo ridondante. unique_ptr<T>(new T())->make_unique<T>()

Nessuno dei motivi comporta il miglioramento dell'efficienza di runtime come make_sharedavviene nell'utilizzo (a causa dell'evitare una seconda allocazione, a scapito dell'utilizzo potenzialmente maggiore della memoria di picco).

* Si prevede che C ++ 17 includa una modifica della regola che significa che questo non è più pericoloso. Vedi i documenti del comitato C ++ P0400R0 e P0145R3 .


Avrebbe più senso dire std::unique_ptre std::shared_ptrsono il motivo per cui possiamo dire alle persone di "non usare mai new".
Timothy Shields,

2
@TimothyShields Sì, questo è ciò che intendo. È solo che in C ++ 11 abbiamo make_sharede così make_uniqueè l'ultimo pezzo che prima mancava.
bames53,

1
In qualche modo potresti citare brevemente, o collegare a, il motivo per cui non si usano temporanee senza nome?
Dan Nissenbaum,

14
In realtà, da stackoverflow.com/a/19472607/368896 , li ho ... Da quella risposta, si consideri la seguente funzione di chiamata f: f(unique_ptr<T>(new T), function_that_can_throw());- per citare la risposta: Il compilatore è permesso di chiamare il numero (in ordine): new T, function_that_can_throw(), unique_ptr<T>(...). Ovviamente se function_that_can_throweffettivamente lancia allora perdi. make_uniqueimpedisce questo caso. Quindi, la mia domanda ha una risposta.
Dan Nissenbaum,

3
Una delle ragioni per cui una volta ho dovuto usare std :: unique_ptr <T> (new T ()) era perché il costruttore di T era privato. Anche se la chiamata a std :: make_unique era in un metodo factory pubblico di classe T, non è stata compilata perché uno dei metodi sottostanti di std :: make_unique non ha potuto accedere al costruttore privato. Non volevo rendere amico quel metodo perché non volevo fare affidamento sull'implementazione di std :: make_unique. Quindi l'unica soluzione era, chiamando new nel mio metodo factory di classe T, e poi avvolgendolo in uno std :: unique_ptr <T>.
Patrick,

14

std::make_uniquee std::make_sharedci sono per due motivi:

  1. In modo che non sia necessario elencare esplicitamente gli argomenti del tipo di modello.
  2. Ulteriore sicurezza di eccezione rispetto all'utilizzo std::unique_ptro ai std::shared_ptrcostruttori. (Vedi la sezione Note qui .)

Non si tratta davvero di efficienza di runtime. C'è qualcosa in più sul blocco di controllo e Tsull'assegnazione allo stesso tempo, ma penso che sia più un bonus e meno una motivazione per l'esistenza di queste funzioni.


Sono anche lì per la sicurezza delle eccezioni.
0x499602D2

@ 0x499602D2 E questo, buona aggiunta. Questa pagina ne parla.
Timothy Shields,

Per i lettori futuri, C ++ 17 non consente l'interlacciamento degli argomenti della funzione, quindi l'argomento per la sicurezza delle eccezioni non è più valido. L'allocazione di memoria per due paralleli std::make_sharedassicurerà che almeno uno di essi venga avvolto nel puntatore intelligente prima che si verifichi l'altra allocazione di memoria, quindi nessuna perdita.
MathBunny il

7

Un motivo per cui dovresti usare std::unique_ptr(new A())o std::shared_ptr(new A())direttamente invece di std::make_*()non essere in grado di accedere al costruttore della classe Aal di fuori dell'ambito corrente.


0

Considera la chiamata di funzione

void function(std::unique_ptr<A>(new A()), std::unique_ptr<B>(new B())) { ... }

Supponiamo che il nuovo abbia A()successo, ma il nuovo B()genera un'eccezione: lo prendi per riprendere la normale esecuzione del tuo programma. Sfortunatamente, lo standard C ++ non richiede che l'oggetto A venga distrutto e la sua memoria allocata: la memoria perde silenziosamente e non c'è modo di ripulirla. Avvolgendo A e B std::make_uniquessi è sicuri che non si verificherà la perdita:

void function(std::make_unique<A>(), std::make_unique<B>()) { ... }

Il punto qui è che std::make_unique<A>e std::make_unique<B>ora sono oggetti temporanei, e la rimozione di oggetti temporanei sia correttamente specificato nella cartella C ++ standard: i loro distruttori verrà attivato e la memoria liberata. Quindi, se puoi, preferisci sempre allocare oggetti usando std::make_uniquee std::make_shared.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.