make_unique e perfetto inoltro


216

Perché non esiste un std::make_uniquemodello di funzione nella libreria C ++ 11 standard? io trovo

std::unique_ptr<SomeUserDefinedType> p(new SomeUserDefinedType(1, 2, 3));

un po 'prolisso. Quanto segue non sarebbe molto più bello?

auto p = std::make_unique<SomeUserDefinedType>(1, 2, 3);

Questo nasconde newbene e menziona il tipo solo una volta.

Ad ogni modo, ecco il mio tentativo di implementazione di make_unique:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

Mi ci è voluto un po 'di tempo per std::forwardcompilare le cose, ma non sono sicuro che sia corretto. È? Cosa std::forward<Args>(args)...significa esattamente ? Cosa ne pensa il compilatore?


1
Abbastanza sicuro che abbiamo già avuto questa discussione prima ... nota anche che unique_ptraccetta un secondo parametro modello che dovresti in qualche modo consentire - che è diverso da shared_ptr.
Kerrek SB,

5
@Kerrek: Non penso che abbia senso parametrizzare make_uniquecon un deleter personalizzato, perché ovviamente si alloca via semplicemente vecchio newe quindi deve usare semplicemente vecchio delete:)
fredoverflow

1
@Fred: è vero. Quindi, la proposta make_uniquesarebbe limitata newall'allocazione ... beh, va bene se vuoi scriverlo, ma posso capire perché qualcosa del genere non fa parte dello standard.
Kerrek SB,

4
In realtà, mi piace usare un make_uniquemodello poiché il costruttore di std::unique_ptrè esplicito, e quindi è prolisso tornare unique_ptrda una funzione. Inoltre, io preferisco usare auto p = make_unique<foo>(bar, baz)quello std::unique_ptr<foo> p(new foo(bar, baz)).
Alexandre C.,

Risposte:


157

Herb Sutter, presidente del comitato di standardizzazione C ++, scrive sul suo blog :

Il fatto che C ++ 11 non includa make_uniqueè in parte una svista e quasi sicuramente verrà aggiunto in futuro.

Fornisce inoltre un'implementazione identica a quella fornita dal PO.

Modifica: std::make_unique ora fa parte di C ++ 14 .


2
Un make_uniquemodello di funzione non garantisce di per sé chiamate sicure ad eccezione. Si basa sulla convenzione, che il chiamante lo utilizza. Al contrario, un rigoroso controllo statico dei tipi (che è la principale differenza tra C ++ e C) si basa sull'idea di applicare la sicurezza, tramite i tipi. E per questo, make_uniquepuò semplicemente essere una classe anziché una funzione. Ad esempio, vedi l' articolo del mio blog di maggio 2010. È anche collegato alla discussione sul blog di Herb.
Saluti e hth. - Alf,

1
@ DavidRodríguez-dribeas: leggi il blog di Herb per capire il problema della sicurezza delle eccezioni. dimenticare "non progettato per essere derivato", è solo spazzatura. invece del mio suggerimento di creare make_uniqueuna classe, ora penso che sia meglio renderla una funzione che produce una make_unique_t, la ragione di ciò è un problema con l'analisi più perversa :-).
Saluti e hth. - Alf

1
@ Cheersandhth.-Alf: Forse abbiamo letto un articolo diverso, perché quello che ho appena letto afferma chiaramente che make_uniqueoffre la forte garanzia di eccezione. O forse stai mescolando lo strumento con il suo utilizzo, nel qual caso nessuna funzione è eccezionalmente sicura. Considera void f( int *, int* ){}, offre chiaramente la no throwgaranzia, ma secondo il tuo ragionamento non è eccezionalmente sicuro, in quanto può essere utilizzato in modo improprio. Peggio ancora, void f( int, int ) {}non è nemmeno un'eccezione sicura !: typedef unique_ptr<int> up; f( *up(new int(5)), *up(new int(10)))...
David Rodríguez - dribeas il

2
@ Cheersandhth.-Alf: Ho chiesto sui problemi di eccezione nel make_unique implementato come sopra e mi si punta ad un articolo di Sutter, l'articolo che il mio google-fu mi ha segnalato stati che make_uniqueoffre la garanzia di un'eccezione forte , che contraddice la vostra dichiarazione di cui sopra. Se hai un articolo diverso, sono interessato a leggerlo. Quindi la mia domanda originale è make_unique: come (come definito sopra) non è sicura l'eccezione? (Sidenote: sì, penso che make_unique migliori la sicurezza delle eccezioni nei luoghi in cui può essere applicato)
David Rodríguez - dribeas,

3
... inoltre non sto perseguendo una funzione meno sicura da usare make_unique. Innanzitutto non vedo come questo non sia sicuro e non vedo come l'aggiunta di un tipo aggiuntivo lo renderebbe più sicuro . Quello che so è che sono interessato a comprendere i problemi che questa implementazione può avere - non ne vedo nessuna - e come un'implementazione alternativa potrebbe risolverli. Quali sono le convenzioni da cui make_uniquedipende? Come useresti il ​​controllo del tipo per applicare la sicurezza? Queste sono le due domande per le quali mi piacerebbe una risposta.
David Rodríguez - dribeas,

78

Bello, ma Stephan T. Lavavej (meglio noto come STL) ha una soluzione migliore per make_unique, che funziona correttamente per la versione dell'array.

#include <memory>
#include <type_traits>
#include <utility>

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::false_type, Args&&... args) {
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::true_type, Args&&... args) {
   static_assert(std::extent<T>::value == 0,
       "make_unique<T[N]>() is forbidden, please use make_unique<T[]>().");

   typedef typename std::remove_extent<T>::type U;
   return std::unique_ptr<T>(new U[sizeof...(Args)]{std::forward<Args>(args)...});
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
   return make_unique_helper<T>(std::is_array<T>(), std::forward<Args>(args)...);
}

Questo può essere visto nel suo video Core C ++ 6 .

Una versione aggiornata della versione di STL di make_unique è ora disponibile come N3656 . Questa versione è stata adottata nella bozza C ++ 14.


make_unique è stato proposto da Stephen T Lavalej nel prossimo aggiornamento standard.
nominatore


perché apportare tutte le modifiche non necessarie xeo, andava bene così com'era. Quel codice era esattamente come lo diceva Stephen T. Lavalej, e lavora per Dinkumware che mantiene la libreria standard. Hai già commentato quel muro, quindi dovresti saperlo.
nominatore

3
L'implementazione per make_uniquedovrebbe andare in un'intestazione. Le intestazioni non devono importare uno spazio dei nomi (vedere l'articolo n. 59 nel libro "Standard di codifica C ++" di Sutter / Alexandrescu). Le modifiche di Xeo aiutano a evitare cattive pratiche.
Bret Kuhns,

sfortunatamente, non supportato da VC2010, anche VC2012 penso, che entrambi non supportano il parametro
varidic tempalte

19

std::make_sharednon è solo una scorciatoia per std::shared_ptr<Type> ptr(new Type(...));. Fa qualcosa di cui non puoi farne a meno.

Per svolgere il proprio lavoro, è std::shared_ptrnecessario allocare un blocco di tracciamento oltre a contenere l'archiviazione per il puntatore effettivo. Tuttavia, poiché std::make_sharedalloca l'oggetto reale, è possibile std::make_sharedallocare sia l'oggetto che il blocco di tracciamento nello stesso blocco di memoria.

Quindi, mentre std::shared_ptr<Type> ptr = new Type(...);due allocazioni di memoria (una per il new, una nel std::shared_ptrblocco di tracciamento) std::make_shared<Type>(...)assegnerebbero un blocco di memoria.

Questo è importante per molti potenziali utenti di std::shared_ptr. L'unica cosa std::make_uniqueche farebbe è essere leggermente più conveniente. Nient'altro.


2
Non è richiesto Suggerimento, ma non richiesto.
Cucciolo,

3
Non è solo per comodità, migliorerebbe anche la sicurezza delle eccezioni in alcuni casi. Vedi la risposta di Kerrek SB per questo.
Piotr99,

19

Mentre nulla ti impedisce di scrivere il tuo aiutante, credo che il motivo principale per fornire make_shared<T>nella libreria sia che in realtà crea un tipo di puntatore condiviso diverso da quello shared_ptr<T>(new T), che è allocato in modo diverso, e non c'è modo di farlo senza l'apposito aiutante.

make_uniqueD'altra parte, il tuo involucro è un semplice zucchero sintattico attorno a newun'espressione, quindi mentre può sembrare piacevole alla vista, non porta nulla newsul tavolo. Correzione: questo non è in realtà vero: avere una chiamata di funzione per racchiudere l' newespressione fornisce una sicurezza eccezionale, ad esempio nel caso in cui si chiama una funzione void f(std::unique_ptr<A> &&, std::unique_ptr<B> &&). Avere due raw newnon seguiti l'uno rispetto all'altro significa che se una nuova espressione fallisce con un'eccezione, l'altra può perdere risorse. Per quanto riguarda il motivo per cui non esiste make_uniqueuno standard: è stato appena dimenticato. (Questo succede di tanto in tanto. Inoltre, non esiste alcun globale std::cbeginnello standard, anche se dovrebbe essercene uno.)

Si noti inoltre che unique_ptraccetta un secondo parametro modello che in qualche modo dovresti consentire; questo è diverso da quello shared_ptr, che utilizza la cancellazione del tipo per memorizzare i deleter personalizzati senza renderli parte del tipo.


1
@FredOverflow: il puntatore condiviso è una classe relativamente complicata; internamente mantiene un blocco di controllo polimorfico di riferimento, ma esistono diversi tipi di blocchi di controllo. shared_ptr<T>(new T)ne usa uno, ne make_shared<T>()usa uno diverso. Consentire che sia una buona cosa, e la versione make-shared è in qualche modo il puntatore condiviso più leggero che puoi ottenere.
Kerrek SB,

15
@FredOverflow: shared_ptralloca un blocco di memoria dinamica per mantenere il conteggio e l'azione "smaltimento" quando si crea un shared_ptr. Se si passa esplicitamente il puntatore, è necessario creare un "nuovo" blocco, se lo si utilizza make_sharedpuò raggruppare l' oggetto e i dati satellitari in un singolo blocco di memoria (uno new) con conseguente allocazione / deallocazione più veloce, meno frammentazione e (normalmente ) miglior comportamento della cache.
Matthieu M.,

2
Penso di aver bisogno di rivedere shared_ptr e amici ...
fredoverflow

4
-1 "D'altra parte, il tuo wrapper make_unique è un semplice zucchero sintattico attorno a una nuova espressione, quindi mentre può sembrare piacevole alla vista, non porta nulla di nuovo sul tavolo." è sbagliato. Offre la possibilità di invocare una funzione sicura di eccezione. Non offre tuttavia una garanzia; per questo, dovrebbe essere di classe in modo che gli argomenti formali possano essere dichiarati di quella classe (Herb lo ha descritto come la differenza tra opt-in e opt-out).
Saluti e hth. - Alf,

1
@ Cheersandhth.-Alf: Sì, è vero. Da allora l'ho capito. Modificherò la risposta.
Kerrek SB,

13

In C ++ 11 ...viene utilizzato (nel codice modello) anche per "espansione del pacchetto".

Il requisito è che tu lo usi come suffisso di un'espressione contenente un pacchetto di parametri non espanso e applicherà semplicemente l'espressione a ciascuno degli elementi del pacchetto.

Ad esempio, basandoti sul tuo esempio:

std::forward<Args>(args)... -> std::forward<int>(1), std::forward<int>(2),
                                                     std::forward<int>(3)

std::forward<Args...>(args...) -> std::forward<int, int, int>(1,2,3)

Quest'ultima essendo errata, penso.

Inoltre, il pacchetto di argomenti non può essere passato a una funzione non espansa. Non sono sicuro di un pacchetto di parametri del modello.


1
Bello vedere questo spiegato. Perché non includere anche il parametro del modello variadic?
Kerrek SB,

@Kerrek: perché non sono così sicuro della sua strana sintassi e non so se molte persone abbiano giocato. Quindi continuerò a quello che so. Potrebbe giustificare una voce FAQ c ++, se qualcuno era abbastanza motivato e ben informato, poiché la sintassi variadica è abbastanza completa.
Matthieu M.,

La sintassi è std::forward<Args>(args)..., che si espande a forward<T1>(x1), forward<T2>(x2), ....
Kerrek SB,

1
: Penso che in forwardrealtà richieda sempre un parametro template, vero?
Kerrek SB,

1
@Howard: giusto! La forzatura dell'istanza attiva l'avviso ( ideone.com/GDNHb )
Matthieu M.

5

Ispirato dall'implementazione di Stephan T. Lavavej, ho pensato che sarebbe stato bello avere un make_unique che supportasse le estensioni dell'array, è su github e mi piacerebbe ricevere commenti. Ti permette di fare questo:

// create unique_ptr to an array of 100 integers
auto a = make_unique<int[100]>();

// create a unique_ptr to an array of 100 integers and
// set the first three elements to 1,2,3
auto b = make_unique<int[100]>(1,2,3); 
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.