Perché usare std :: make_unique in C ++ 17?


96

Per quanto ho capito, C ++ 14 è stato introdotto std::make_uniqueperché, a causa della mancata specificazione dell'ordine di valutazione dei parametri, questo non era sicuro:

f(std::unique_ptr<MyClass>(new MyClass(param)), g()); // Syntax A

(Spiegazione: se la valutazione alloca prima la memoria per il puntatore non elaborato, quindi chiama g()e viene generata un'eccezione prima della std::unique_ptrcostruzione, la memoria perde.)

La chiamata std::make_uniqueera un modo per limitare l'ordine delle chiamate, rendendo così le cose sicure:

f(std::make_unique<MyClass>(param), g());             // Syntax B

Da allora, C ++ 17 ha chiarito l'ordine di valutazione, rendendo sicura anche la sintassi A, quindi ecco la mia domanda: c'è ancora un motivo per utilizzare il costruttore di std::make_uniqueover std::unique_ptrin C ++ 17? Puoi fare qualche esempio?

A partire da ora, l'unico motivo che posso immaginare è che consente di digitare MyClasssolo una volta (supponendo che non sia necessario fare affidamento sul polimorfismo con std::unique_ptr<Base>(new Derived(param))). Tuttavia, questo sembra un motivo piuttosto debole, specialmente quando std::make_uniquenon consente di specificare un deleter mentre std::unique_ptril costruttore di lo fa.

E giusto per essere chiari, non sto sostenendo la rimozione std::make_uniquedalla libreria standard (mantenerla ha senso almeno per la compatibilità con le versioni precedenti), ma piuttosto mi chiedo se ci sono ancora situazioni in cui è fortemente preferitostd::unique_ptr


4
Tuttavia, questo sembra un motivo piuttosto debole -> Perché è un motivo debole? Riduce efficacemente la duplicazione del codice di tipo. Per quanto riguarda il deleter, quanto spesso utilizzi un deleter personalizzato quando lo usi std::unique_ptr? Non è un argomento contromake_unique
llllllllll

2
Dico che è una ragione debole perché se non ci fosse std::make_uniquein primo luogo, non penso che sarebbe un motivo sufficiente per aggiungerlo all'STL, specialmente quando è una sintassi che è meno espressiva rispetto all'utilizzo del costruttore, non di più
Eterno

1
Se hai un programma, creato in c ++ 14, che usa make_unique, non vuoi che la funzione venga rimossa da stl. O se vuoi che sia compatibile con le versioni precedenti.
Serge

2
@Serge Questo è un buon punto, ma è un po 'oltre l'oggetto della mia domanda. Apporterò una modifica per renderlo più chiaro
Eternal

1
@Eternal, smetti di fare riferimento alla libreria standard C ++ come STL in quanto non è corretto e crea confusione. Vedere stackoverflow.com/questions/5205491/...
Marandil

Risposte:


73

Hai ragione che il motivo principale è stato rimosso. Ci sono ancora le nuove linee guida per non usare e che sono meno motivi di digitazione (non è necessario ripetere il tipo o usare la parola new). Certo, questi non sono argomenti forti, ma mi piace davvero non vedere newnel mio codice.

Inoltre, non dimenticare la coerenza. Dovresti assolutamente usarlo, make_sharedquindi l'uso make_uniqueè naturale e si adatta allo schema. È quindi banale passare std::make_unique<MyClass>(param)a std::make_shared<MyClass>(param)(o viceversa) dove la sintassi A richiede molto più di una riscrittura.


40
@reggaeguitar Se vedo un newdevo fermarmi e pensare: per quanto tempo vivrà questo puntatore? L'ho gestito correttamente? Se c'è un'eccezione, tutto è stato ripulito correttamente? Mi piacerebbe non farmi queste domande e sprecare il mio tempo su di esso e se non lo uso new, non devo fare quelle domande.
NathanOliver

5
Immagina di eseguire un grep su tutti i file sorgente del tuo progetto e di non trovarne uno new. Non sarebbe meraviglioso?
Sebastian Mach

5
Il vantaggio principale della linea guida "non usare nuove" è che è semplice, quindi è una linea guida facile da dare agli sviluppatori meno esperti con cui potresti lavorare. All'inizio non me ne ero reso conto, ma questo ha un valore in sé e per sé
Eternal

@NathanOliver In realtà non devi assolutamente usare std::make_shared- immagina un caso in cui l'oggetto allocato è grande e ci sono molti std::weak_ptrpunti che lo puntano: sarebbe meglio lasciare che l'oggetto venga eliminato non appena l'ultimo condiviso il puntatore viene distrutto e vive solo con una piccola area condivisa.
Dev Null

1
@NathanOliver non lo faresti. Quello di cui sto parlando è lo svantaggio di std::make_shared stackoverflow.com/a/20895705/8414561 dove la memoria che è stata utilizzata per memorizzare l'oggetto non può essere liberata fino a quando l'ultimo non std::weak_ptrè sparito (anche se tutti std::shared_ptrpuntano ad esso (e di conseguenza l'oggetto stesso) sono già stati distrutti).
Dev Null

50

make_uniquedistingue Tda T[]e T[N], unique_ptr(new ...)non lo fa.

È possibile ottenere facilmente un comportamento indefinito passando un puntatore che è stato modificato new[]in a unique_ptr<T>, o passando un puntatore che è stato modificato newin a unique_ptr<T[]>.


È peggio: non solo non lo fa, è addirittura incapace di farlo.
Deduplicatore

21

Il motivo è avere un codice più breve senza duplicati. Confrontare

f(std::unique_ptr<MyClass>(new MyClass(param)), g());
f(std::make_unique<MyClass>(param), g());

Si salva MyClass, newe bretelle. Il costo è di solo un carattere più a rendere in confronto con ptr .


2
Bene, come ho detto nella domanda, posso vedere che è meno digitando con una sola menzione MyClass, ma mi chiedevo se ci fosse una ragione più forte per usarlo
Eternal

2
In molti casi la guida alla detrazione aiuterebbe ad eliminare la <MyClass>parte nella prima variante.
AnT

9
È già stato detto nei commenti per altre risposte, ma mentre c ++ 17 ha introdotto la deduzione del tipo di modello per i costruttori, nel caso std::unique_ptrnon sia consentito. Ha a che fare con la distinzione std::unique_ptr<T>estd::unique_ptr<T[]>
Eternal

19

Ogni utilizzo di newdeve essere verificato con estrema attenzione per verificarne la correttezza a vita; viene cancellato? Solo una volta?

Ogni utilizzo di make_uniquenon per quelle caratteristiche extra; fintanto che l'oggetto proprietario ha una durata "corretta", ricorsivamente fa sì che il puntatore univoco abbia "corretto".

Ora, è vero che unique_ptr<Foo>(new Foo())è identico in tutti i modi da 1 a make_unique<Foo>(); richiede solo un più semplice "grep il tuo codice sorgente per tutti gli usi newper controllarli".


In realtà sono una bugia nel caso generale. L'inoltro perfetto non è perfetto, {}init predefinito, gli array sono tutte eccezioni.


Tecnicamente unique_ptr<Foo>(new Foo)non è del tutto identico a make_unique<Foo>()... quest'ultimo new Foo()sì, ma per il resto sì.
Barry

@barry true, è possibile un nuovo operatore sovraccarico.
Yakk - Adam Nevraumont

@dedup che schifosa stregoneria in C ++ 17 è questa?
Yakk - Adam Nevraumont

2
@Deduplicator mentre c ++ 17 ha introdotto la deduzione del tipo di modello per i costruttori, nel caso std::unique_ptrnon sia consentito. Se ha a che fare con la distinzione std::unique_ptr<T>estd::unique_ptr<T[]>
Eternal

@ Yakk-AdamNevraumont Non intendevo sovraccaricare il nuovo, intendevo solo default-init vs value-init.
Barry

0

Da allora, C ++ 17 ha chiarito l'ordine di valutazione, rendendo anche la sintassi A sicura

Non è davvero abbastanza buono. Affidarsi a una clausola tecnica di recente introduzione come garanzia di sicurezza non è una pratica molto robusta:

  • Qualcuno potrebbe compilare questo codice in C ++ 14.
  • Incoraggeresti l'uso di raw newaltrove, ad esempio copiando e incollando il tuo esempio.
  • Come suggerisce SM, poiché esiste la duplicazione del codice, un tipo potrebbe essere modificato senza che l'altro venga modificato.
  • Un qualche tipo di refactoring IDE automatico potrebbe spostarlo newaltrove (ok, garantito, non ci sono molte possibilità).

In generale, è una buona idea che il codice sia appropriato / robusto / chiaramente valido senza ricorrere alla stratificazione del linguaggio, cercando clausole tecniche minori o oscure nello standard.

(questo è essenzialmente lo stesso argomento che ho fatto qui sull'ordine di distruzione della tupla.)


-1

Considera la funzione void (std :: unique_ptr (new A ()), std :: unique_ptr (new B ())) {...}

Supponi che new A () abbia successo, ma new B () generi 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 deallocata: la memoria perde silenziosamente e non c'è modo di pulirla. Avvolgendo A e B in std :: make_uniques sei sicuro che la perdita non si verificherà:

void function (std :: make_unique (), std :: make_unique ()) {...} Il punto qui è che std :: make_unique e std :: make_unique sono ora oggetti temporanei e la pulizia degli oggetti temporanei è specificata correttamente in lo standard C ++: i loro distruttori verranno attivati ​​e la memoria liberata. Quindi, se puoi, preferisci sempre allocare oggetti usando std :: make_unique e std :: make_shared.


4
L'autore ha esplicitamente specificato in questione che in C ++ 17 le fughe di notizie non si verificheranno più. "Da allora, C ++ 17 ha chiarito l'ordine di valutazione, rendendo sicura anche la sintassi A, quindi ecco la mia domanda: (...)". Non hai risposto alla sua domanda.
R2RT
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.