2012-01-03 30 views
47

Ho scritto un metodo factory statico che restituisce un nuovo oggetto Foobar popolato da un altro oggetto dati. Recentemente sono stato ossessionato dalla semantica della proprietà e mi sto chiedendo se sto trasmettendo il messaggio giusto facendo in modo che questo metodo di fabbrica restituisca un unique_ptr.In pratica non è possibile restituire unique_ptr per il puntatore come la semantica della proprietà?

class Foobar { 
public: 
    static unique_ptr<Foobar> factory(DataObject data); 
} 

Il mio intento è di dire al codice client di essere il proprietario del puntatore. Senza un puntatore intelligente, vorrei semplicemente restituire Foobar*. Vorrei, tuttavia, far rispettare questa memoria per evitare potenziali bug, quindi unique_ptr sembrava una soluzione appropriata. Se il cliente desidera estendere la durata del puntatore, chiama semplicemente .release() una volta ottenuto il numero unique_ptr.

Foobar* myFoo = Foobar::factory(data).release(); 

La mia domanda è disponibile in due parti:

  1. Questo approccio trasmettere la semantica di proprietà corrette?
  2. È una "cattiva pratica" restituire unique_ptr anziché un puntatore non elaborato?
+0

stai tornando un unqiue_ptr per dire al cliente che possiedono il puntatore? Questo è esattamente l'opposto di quello che mi aspetterei (dal momento che devono prendere esplicitamente la proprietà del puntatore univoco). – jknupp

+1

Si potrebbe preferire usare move-semantics (se si è in grado di usare C++ 11). Con questo, spetta all'utente decidere come prolungare la durata dell'oggetto creato dalla fabbrica. – evnu

+1

@evnu è qualcosa che viene fatto automaticamente per te, no? –

risposta

58

Il ripristino di un std::unique_ptr da un metodo di fabbrica è perfetto e dovrebbe essere una pratica consigliata. Il messaggio che trasmette è (IMO): Ora sei l'unico proprietario di questo oggetto. Inoltre, per tua comodità, l'oggetto sa come distruggere se stesso.

Penso che sia molto meglio restituire un puntatore non elaborato (in cui il client deve ricordare come e se eliminare questo puntatore).

Tuttavia, non capisco il tuo commento sul rilascio del puntatore per estendere la sua durata. In generale raramente vedo una ragione per chiamare release su uno smartpointer, poiché penso che i puntatori debbano essere sempre gestiti da una sorta di struttura RAII (solo l'unica situazione in cui chiamo release consiste nel mettere il puntatore in una diversa struttura di gestione, ad es. un unique_ptr con un deleter diverso, dopo che ho fatto qualcosa per garantire una pulizia aggiuntiva).

Pertanto il cliente può (e dovrebbe) semplicemente memorizzare il unique_ptr da qualche parte (ad esempio un altro unique_ptr, che è stata mossa costruito da quello restituito) fintanto che hanno bisogno l'oggetto (o un shared_ptr, se hanno bisogno di più copie del puntatore).Quindi il codice lato client dovrebbe essere più simile a questo:

std::unique_ptr<FooBar> myFoo = Foobar::factory(data); 
//or: 
std::shared_ptr<FooBar> myFoo = Foobar::factory(data); 

Personalmente vorrei anche aggiungere una typedef per il tipo di puntatore restituito (in questo caso std::unique_ptr<Foobar>) ed o il deleter utilizzato (in questo caso std :: default_deleter) a il tuo oggetto di fabbrica. Ciò rende più facile se in seguito si decide di modificare l'allocazione del puntatore (e quindi è necessario un metodo diverso per la distruzione del puntatore, che sarà visibile come un secondo parametro del modello di std::unique_ptr). quindi vorrei fare qualcosa di simile:

class Foobar { 
public: 
    typedef std::default_deleter<Foobar>  deleter; 
    typedef std::unique_ptr<Foobar, deleter> unique_ptr; 

    static unique_ptr factory(DataObject data); 
} 

Foobar::unique_ptr myFoo = Foobar::factory(data); 
//or: 
std::shared_ptr<Foobar> myFoo = Foobar::factory(data); 
+3

Non intendevo che la mia menzione di 'release()' fosse fuorviante o confusa, mi dispiace. Questo nuovo codice sta entrando in un'applicazione abbastanza grande esistente (1-2 milioni di righe di C++) che non utilizza alcun puntatore intelligente (diverso da COM).So che gli altri membri della mia squadra saranno preoccupati se non c'è un modo ragionevole per raggiungere il puntatore raw se il codice legacy lo rende effettivamente "richiesto". Ovviamente, darò gli avvertimenti appropriati e raccomanderò di attenermi a unique_ptr/shared_ptr quando possibile. Mi piace molto la tua idea typedef, che distingue la tua risposta da quella di James McNellis. Grazie per gli approfondimenti. –

17

A std::unique_ptr possiede univocamente l'oggetto a cui punta. Dice "Posseggo questo oggetto, e nessun altro lo fa".

Questo è esattamente ciò che stai cercando di esprimere: stai dicendo "chiamante di questa funzione: ora sei l'unico proprietario di questo oggetto, fallo come ti pare, la sua vita è una tua responsabilità".

+0

In effetti, ma perché non farlo restituire un 'std :: shared_pointer <>'? Raggiunge lo stesso effetto ma consente più copie del puntatore. –

+8

@ AndréCaron: Perché imporre l'uso di 'std :: shared_ptr' se tutto ciò che si vuole fare è trasferire la proprietà al chiamante? Lascia che il chiamante assegni la proprietà dell'oggetto a 'std :: shared_ptr' se lo desidera (o un altro tipo di puntatore intelligente, se il chiamante lo desidera). –

+7

@ André Caron: la ragione è fondamentalmente la prestazione. La factory non può sapere se il client aggiungerà la funzionalità aggiunta da 'std :: shared_ptr' a' std :: unique_ptr', quindi perché sacrificare le prestazioni per alcune funzionalità, che potrebbero non essere necessarie. Poiché 'shared_ptr' può essere costruito e assegnato da' unique_ptr' non c'è davvero alcun vantaggio – Grizzly

6

Esso trasmette esattamente la semantica corretta ed è il modo in cui penso che tutte le fabbriche in C++ dovrebbero funzionare: std::unique_ptr<T> non impone alcun tipo di semantica della proprietà ed è estremamente economico.