2012-03-14 39 views
45

Sto provando a "modernizzare" un codice esistente.Passaggio di unique_ptr alle funzioni

  • Ho una classe che attualmente ha una variabile membro "Dispositivo * dispositivo_".
  • Si utilizza nuovo per creare un'istanza in un codice di inizializzazione e ha un "delete device_" nel distruttivo.
  • Funzioni membro di questa classe chiamata molte altre funzioni che richiedono un dispositivo * come parametro.

Questo funziona bene, ma per "modernizzare" il mio codice ho pensato che dovrebbe modificare la variabile per essere definito "std::unique_ptr<Device> device_" e rimuovere la chiamata esplicita di cancellare, il che rende il codice più sicuro e generalmente migliore.

La mia domanda è questa -

  • Come devo quindi far passare il dispositivo _ variabile per tutte le funzioni che ne hanno bisogno come paramater?

Posso chiamare .get per ottenere il puntatore raw in ogni chiamata di funzione. Ma sembra brutto e spreca alcuni dei motivi per utilizzare un unique_ptr in primo luogo.

O posso cambiare ogni funzione in modo che invece di prendere un parametro di tipo "Device *" ora vuole un paramater di tipo "std :: unique_ptr &". Quale (per me) offusca in qualche modo i prototipi di funzione e li rende difficili da leggere.

Qual è la migliore pratica per questo? Ho perso altre opzioni?

+3

Perché questo cambiamento rendere il codice più sicuro e generalmente meglio? Dalla tua descrizione, direi che è vero il contrario. –

+0

@James Kanze I agree, unique_ptr non sembra comprare nulla in questo caso. – juanchopanza

+2

Potresti avere ragione. Mi piace la "sicurezza" di unique_ptr in quanto non devi ricordarti di eliminarlo, ma in questo caso sembra avere dei costi. – jcoder

risposta

44

In moderna C stile ++, ci sono due chiavi concetti:

  • Proprietà
  • Nullità

proprietà è sul proprietario di un oggetto/risorsa (in questo caso , un'istanza di Device). I vari std::unique_ptr, boost::scoped_ptr o std::shared_ptr riguardano la proprietà.

Nullity è molto più semplice tuttavia: esprime solo se un dato oggetto potrebbe essere nullo e non si preoccupa di nient'altro, e certamente non di proprietà!


Si erano destra per spostare l'implementazione della classe verso unique_ptr (in generale), anche se si consiglia un puntatore intelligente con la semantica profonda di copia, se il vostro obiettivo è quello di attuare un Pimpl.

Ciò dimostra chiaramente che la tua classe è l'unica responsabile di questo pezzo di memoria e si occupa in modo ordinato di tutti i vari modi in cui la memoria potrebbe aver perso il contrario.


D'altra parte, la maggior parte degli utenti delle risorse non potrebbe interessare di meno la sua proprietà.

Fintanto che una funzione non mantiene un riferimento a un oggetto (memorizzarlo in una mappa o qualcosa del genere), tutto ciò che conta è che la durata dell'oggetto supera la durata della chiamata di funzione.

Quindi, scegliere come passare il parametro dipende dalla sua possibile Nullità:

  • mai nulla? Passa un riferimento
  • Forse null?Passare un puntatore , un semplice puntatore nudo o una classe puntatore-simile (con una trappola sul nulla, per esempio)

+0

Hmm, riferimenti ... Potrei fare in modo che le mie funzioni prendessero i parametri "Device & device" e quindi nel codice chiamante usare * device_ in quanto funzionerà ancora con unique_ptr e so che device_ non può essere nullo (e può testarlo con asserzioni). Ora ho bisogno di riflettere su questo per un po 'e decidere se è brutto o elegante! – jcoder

+2

@JohnB: beh, il mio punto di vista biascicato è che sradicare la possibilità di nullità in fase di compilazione è molto più sicuro :) –

+6

@JohnB: Completamente d'accordo con Matthieu qui, e vorrei anche andare oltre, perché vuoi dinamicamente allocare un oggetto la cui durata è vincolata dalla durata dell'oggetto che contiene il puntatore? Perché non dichiararlo come membro normale con archiviazione automatica? –

7

Vorrei utilizzare std::unique_ptr const&. L'uso di un riferimento non const darà alla funzione chiamata la possibilità di reimpostare il puntatore.
Penso che questo sia un buon modo per esprimere che la funzione chiamata può usare il puntatore ma nient'altro.
Quindi per me questo renderà l'interfaccia più facile da leggere. So che non devo giocherellare con il puntatore passato a me.

+0

Quindi, per essere chiari, aggiungere cost ci vorrà dire che non posso cambiare l'unique_ptr in alcun modo, ma io * posso * chiamare le funzioni non const dell'oggetto contenuto tramite l'unique_ptr? – jcoder

+0

@JohnB: Sì il const impedisce alla funzione chiamata di chiamare e.g reset su unique_ptr. Ma purché il puntatore all'interno non sia const, è possibile chiamare le funzioni non const. – mkaes

+0

Sì, è possibile. get() metodo const di unique_ptr restituisce puntatore non-const all'oggetto custodito da unique_ptr – zabulus

1

Questo potrebbe non essere fattibile per voi, ma sostituire ogni occorrenza di Device* per const unique_ptr<Device>& è un buon inizio.

Ovviamente non è possibile copiare unique_ptr s e non si desidera spostarlo. La sostituzione con un riferimento a unique_ptr consente al corpo dei corpi delle funzioni esistenti di continuare a funzionare.

Ora c'è una trappola, è necessario passare const & per evitare che i callees eseguano unique_ptr.reset() o unique_ptr().release(). Si noti che questo ancora passa un puntatore modificabile al dispositivo. Con questa soluzione non è semplice passare un puntatore o un riferimento a const Device.

+0

Dato che const unique_ptr ed è ingombrante, lo digitaremmo su Device_t o simile. –

14

Dipende davvero. Se una funzione deve assumere la proprietà di unique_ptr, la sua firma deve assumere un valore unique_ptr<Device> bv e il chiamante deve utilizzare il puntatore std::move. Se la proprietà non è un problema, manterrò la firma del puntatore raw e passerò il pointer unique_ptr usando get(). Questo non è brutto se la funzione in questione non assume la proprietà.

+0

Sì, non voglio prendere la propria, basta usare l'oggetto puntato nella funzione. – jcoder

+2

È anche possibile passare un riferimento const a unique_ptr se la proprietà non è un problema. Alcune persone usano la dicotomia puntatore-riferimento per simulare la semantica dei parametri formale in-out di Pascal (guida allo stile di Google C++). –

2

La procedura consigliata probabilmente non è quella di utilizzare std::unique_ptr in questo caso, sebbene dipenda. (In genere non si dovrebbe avere più di un puntatore non elaborato su un oggetto allocato dinamicamente in una classe, sebbene dipenda anche da questo . L'unica cosa che non si vuole fare in questo caso è passando intorno a (e come hai notato, std::unique_ptr<> const& è un po 'ingombrante e offuscante). Se questo è l'unico puntatore assegnato in modo dinamico nell'oggetto, inserisco semplicemente con il puntatore raw e il delete nel distruttore. Se ci sono diversi indicatori di questo tipo, prenderei in considerazione il relegare ciascuno di essi in una classe base separata (dove possono ancora essere puntatori raw).

Problemi correlati