Quali implementazioni C ++ Smart Pointer sono disponibili?


121

Confronti, vantaggi, svantaggi e quando utilizzarli?

Questo è uno spin-off di un thread di garbage collection in cui quella che pensavo fosse una semplice risposta ha generato molti commenti su alcune specifiche implementazioni di puntatori intelligenti, quindi è sembrato che valesse la pena iniziare un nuovo post.

In definitiva la domanda è: quali sono le varie implementazioni di puntatori intelligenti in C ++ là fuori e come si confrontano? Solo semplici pro e contro o eccezioni e trucchi per qualcosa che altrimenti potresti pensare dovrebbe funzionare.

Ho pubblicato alcune implementazioni che ho usato o almeno sorvolato e considerato di utilizzare come risposta di seguito e la mia comprensione delle loro differenze e somiglianze che potrebbero non essere accurate al 100%, quindi sentiti libero di verificarmi o correggermi se necessario.

L'obiettivo è conoscere alcuni nuovi oggetti e librerie o correggere il mio utilizzo e la comprensione delle implementazioni esistenti già ampiamente in uso e finire con un riferimento decente per gli altri.


5
Penso che questo dovrebbe essere ripubblicato come una risposta a questa domanda e la domanda trasformata in una domanda reale. Altrimenti, sento che le persone chiuderanno questo come "non una vera domanda".
strager

3
Ci sono tutti i tipi di altri puntatori intelligenti, ad esempio i puntatori intelligenti ATL o OpenSceneGraphosg::ref_ptr .
James McNellis

11
C'è una domanda qui?
Cody Grey

6
Penso che tu abbia frainteso std::auto_ptr. std::auto_ptr_refè un dettaglio di design di std::auto_ptr. std::auto_ptrnon ha nulla a che fare con la garbage collection, il suo scopo principale è specificamente quello di consentire il trasferimento di proprietà sicuro rispetto alle eccezioni, specialmente in situazioni di chiamata di funzione e ritorno di funzione. std::unique_ptrpuò solo risolvere i "problemi" che citi con i contenitori standard perché il C ++ è cambiato per consentire una distinzione tra spostamento e copia e i contenitori standard sono cambiati per trarne vantaggio.
CB Bailey

3
Dici di non essere un esperto di puntatori intelligenti, ma il tuo riepilogo è piuttosto esauriente e corretto (tranne per il piccolo cavillo auto_ptr_refsull'essere un dettaglio di implementazione). Tuttavia, sono d'accordo sul fatto che dovresti pubblicare questo come risposta e riformulare la domanda in modo che sia una domanda reale. Questo può quindi servire come riferimento futuro.
Konrad Rudolph

Risposte:


231

C ++ 03

std::auto_ptr- Forse uno degli originali soffriva della sindrome della prima stesura fornendo solo strutture limitate per la raccolta dei rifiuti. Il primo svantaggio è che richiede deletela distruzione rendendoli inaccettabili per contenere oggetti allocati da array ( new[]). Prende la proprietà del puntatore, quindi due puntatori automatici non dovrebbero contenere lo stesso oggetto. L'assegnazione trasferirà la proprietà e reimposterà il puntatore automatico rvalue su un puntatore nullo. Il che porta forse al peggior inconveniente; non possono essere utilizzati all'interno di contenitori STL a causa della suddetta impossibilità di essere copiati. Il colpo finale a qualsiasi caso d'uso è che dovrebbero essere deprecati nel prossimo standard di C ++.

std::auto_ptr_ref- Questo non è un puntatore intelligente, in realtà è un dettaglio di progettazione utilizzato insieme a std::auto_ptrper consentire la copia e l'assegnazione in determinate situazioni. In particolare, può essere utilizzato per convertire un non-const std::auto_ptrin un lvalue utilizzando il trucco di Colvin-Gibbons noto anche come costruttore di mosse per trasferire la proprietà.

Al contrario, forse std::auto_ptrnon è stato concepito per essere utilizzato come puntatore intelligente generico per la raccolta automatica dei rifiuti. La maggior parte della mia comprensione e delle mie supposizioni limitate si basa sull'uso efficace di auto_ptr di Herb Sutter e lo uso regolarmente anche se non sempre nel modo più ottimizzato.


C ++ 11

std::unique_ptr- Questo è il nostro amico che lo sostituirà std::auto_ptrsarà abbastanza simile tranne che con i miglioramenti chiave per correggere i punti deboli std::auto_ptrcome lavorare con gli array, protezione lvalue tramite il costruttore di copie private, essere utilizzabile con contenitori STL e algoritmi, ecc. Poiché è un sovraccarico delle prestazioni e l'impronta di memoria è limitata, questo è un candidato ideale per sostituire, o forse più appropriatamente descritto come proprietario di puntatori grezzi. Come "unico" implica che esiste un solo proprietario del puntatore proprio come il precedente std::auto_ptr.

std::shared_ptr- Credo che sia basato su TR1 e boost::shared_ptrma migliorato per includere anche l'aliasing e l'aritmetica dei puntatori. In breve, avvolge un puntatore intelligente con conteggio di riferimento attorno a un oggetto allocato dinamicamente. Poiché "condiviso" implica che il puntatore può essere di proprietà di più di un puntatore condiviso quando l'ultimo riferimento dell'ultimo puntatore condiviso esce dall'ambito, l'oggetto verrà eliminato in modo appropriato. Questi sono anche thread-safe e nella maggior parte dei casi possono gestire tipi incompleti. std::make_sharedpuò essere utilizzato per costruire in modo efficiente std::shared_ptrun'allocazione con uno heap utilizzando l'allocatore predefinito.

std::weak_ptr- Allo stesso modo basato su TR1 e boost::weak_ptr. Questo è un riferimento a un oggetto di proprietà di a std::shared_ptre quindi non impedirà l'eliminazione dell'oggetto se il std::shared_ptrconteggio dei riferimenti scende a zero. Per avere accesso al puntatore grezzo devi prima accedere std::shared_ptra chiamando lockche restituirà un vuoto std::shared_ptrse il puntatore di proprietà è scaduto ed è già stato distrutto. Ciò è principalmente utile per evitare conteggi di riferimenti sospesi indefiniti quando si utilizzano più puntatori intelligenti.


Incremento

boost::shared_ptr- Probabilmente il più facile da usare negli scenari più diversi (STL, PIMPL, RAII, ecc.) Questo è un puntatore intelligente conteggio referenziato condiviso. Ho sentito alcune lamentele sulle prestazioni e le spese generali in alcune situazioni, ma devo averle ignorate perché non ricordo quale fosse l'argomento. Apparentemente era abbastanza popolare da diventare un oggetto C ++ standard in sospeso e non vengono in mente svantaggi rispetto alla norma per quanto riguarda i puntatori intelligenti.

boost::weak_ptr- In modo molto simile alla descrizione precedente di std::weak_ptr, basata su questa implementazione, ciò consente un riferimento non proprietario a un file boost::shared_ptr. Non sorprende che tu chiami lock()per accedere al puntatore condiviso "forte" e devi controllare che sia valido in quanto potrebbe essere già stato distrutto. Assicurati solo di non memorizzare il puntatore condiviso restituito e lascia che esca dall'ambito non appena hai finito, altrimenti torni subito al problema del riferimento ciclico in cui i conteggi dei riferimenti si bloccheranno e gli oggetti non verranno distrutti.

boost::scoped_ptr- Questa è una semplice classe di puntatori intelligenti con un piccolo overhead probabilmente progettata per un'alternativa più performante a boost::shared_ptrquando utilizzabile. È paragonabile std::auto_ptrsoprattutto al fatto che non può essere utilizzato in modo sicuro come elemento di un contenitore STL o con più puntatori allo stesso oggetto.

boost::intrusive_ptr- Non l'ho mai usato, ma dalla mia comprensione è progettato per essere utilizzato durante la creazione di classi compatibili con il puntatore intelligente. Devi implementare tu stesso il conteggio dei riferimenti, dovrai anche implementare alcuni metodi se vuoi che la tua classe sia generica, inoltre dovresti implementare la tua thread safety. Tra i lati positivi, questo probabilmente ti offre il modo più personalizzato di scegliere e scegliere esattamente quanto o quanto poca "intelligenza" desideri. intrusive_ptrè in genere più efficiente di shared_ptrquanto ti consente di avere una singola allocazione di heap per oggetto. (grazie Arvid)

boost::shared_array- Questo è un boost::shared_ptrarray per. Fondamentalmente new [], operator[]e ovviamente delete []sono al forno. Questo può essere usato nei contenitori STL e per quanto ne so fa tutto boost:shared_ptr, anche se non puoi usarli boost::weak_ptrcon questi. È tuttavia possibile utilizzare in alternativa un boost::shared_ptr<std::vector<>>per funzionalità simili e per riacquistare la capacità di utilizzare boost::weak_ptrper i riferimenti.

boost::scoped_array- Questo è un boost::scoped_ptrarray per. Come con boost::shared_arraytutta la bontà dell'array necessaria è incorporata. Questo non è copiabili e quindi non può essere utilizzato nei contenitori STL. Ho trovato quasi ovunque ti ritrovi a voler usare questo che probabilmente potresti semplicemente usare std::vector. Non ho mai determinato quale sia effettivamente più veloce o abbia meno overhead, ma questo array con scope sembra molto meno coinvolto di un vettore STL. Quando vuoi mantenere l'allocazione sullo stack, considera boost::arrayinvece.


Qt

QPointer- Introdotto in Qt 4.0, questo è un puntatore intelligente "debole" che funziona solo con QObjectclassi derivate e che nel framework Qt è quasi tutto, quindi non è davvero una limitazione. Tuttavia ci sono limitazioni, vale a dire che non fornisce un puntatore "forte" e sebbene tu possa controllare se l'oggetto sottostante è valido, isNull()potresti scoprire che il tuo oggetto viene distrutto subito dopo aver superato quel controllo, specialmente in ambienti multi-thread. Le persone Qt lo considerano deprecato, credo.

QSharedDataPointer- Questo è un puntatore intelligente "forte" potenzialmente paragonabile a boost::intrusive_ptrsebbene abbia alcuni thread safety incorporati, ma richiede di includere metodi di conteggio dei riferimenti ( refe deref) che puoi fare sottoclasse QSharedData. Come con gran parte di Qt, gli oggetti vengono utilizzati al meglio attraverso un'ampia eredità e sottoclasse tutto sembra essere il design previsto.

QExplicitlySharedDataPointer- Molto simile a QSharedDataPointertranne che non chiama implicitamente detach(). Chiamerei questa versione 2.0 in QSharedDataPointerquanto quel leggero aumento del controllo su esattamente quando staccare dopo che il conteggio dei riferimenti è sceso a zero non vale particolarmente un oggetto completamente nuovo.

QSharedPointer- Conteggio dei riferimenti atomici, thread safe, puntatore condivisibile, eliminazioni personalizzate (supporto array), sembra tutto ciò che dovrebbe essere un puntatore intelligente. Questo è ciò che utilizzo principalmente come puntatore intelligente in Qt e lo trovo paragonabile boost:shared_ptranche se probabilmente molto più sovraccarico come molti oggetti in Qt.

QWeakPointer- Senti uno schema ricorrente? Proprio come std::weak_ptre boost::weak_ptrquesto è usato insieme a QSharedPointerquando hai bisogno di riferimenti tra due puntatori intelligenti che altrimenti farebbero sì che i tuoi oggetti non vengano mai cancellati.

QScopedPointer- Questo nome dovrebbe anche sembrare familiare e in realtà era basato su, a boost::scoped_ptrdifferenza delle versioni Qt, di puntatori condivisi e deboli. Funziona per fornire un puntatore intelligente per un singolo proprietario senza il cui sovraccarico QSharedPointerlo rende più adatto per compatibilità, codice sicuro dalle eccezioni e tutte le cose che potresti usare std::auto_ptro boost::scoped_ptrper.


1
due cose che penso valga la pena menzionare: intrusive_ptrè in genere più efficiente di shared_ptr, poiché consente di avere una singola allocazione di heap per oggetto. shared_ptrnel caso generale allocherà un piccolo oggetto heap separato per i contatori di riferimento. std::make_sharedpuò essere utilizzato per ottenere il meglio da entrambi i mondi. shared_ptrcon una sola allocazione di heap.
Arvid

Ho una domanda forse non correlata: la garbage collection può essere implementata semplicemente sostituendo tutti i puntatori con shared_ptrs? (Senza contare la risoluzione dei riferimenti ciclici)
Seth Carnegie

@Seth Carnegie: Non tutti i puntatori punteranno a qualcosa allocato nel negozio gratuito.
In silico

2
@the_mandrill Ma funziona se il distruttore della classe proprietaria è definito in un'unità di traduzione separata (file .cpp) rispetto al codice client, che nell'idioma Pimpl viene comunque fornito. Perché questa unità di traduzione di solito conosce la definizione completa del Pimpl e quindi il suo distruttore (quando distrugge l'auto_ptr) distrugge correttamente il Pimpl. Avevo anche paura di questo quando ho visto quegli avvertimenti, ma l'ho provato e funziona (il distruttore di Pimpl viene chiamato). PS .: per favore usa la sintassi @ per vedere le risposte.
Christian Rau

2
L'utilità dell'elenco è stata aumentata aggiungendo collegamenti appropriati ai documenti.
ulidtko


1

Oltre a quelli indicati, ce ne sono anche alcuni orientati alla sicurezza:

SaferCPlusPlus

mse::TRefCountingPointerè un puntatore intelligente per il conteggio dei riferimenti come std::shared_ptr. La differenza è che mse::TRefCountingPointerè più sicuro, più piccolo e più veloce, ma non ha alcun meccanismo di sicurezza del filo. Ed è disponibile nelle versioni "non nullo" e "fisso" (non retargetabile) che si può tranquillamente presumere che punti sempre a un oggetto allocato in modo valido. Quindi, in sostanza, se il tuo oggetto di destinazione è condiviso tra thread asincroni std::shared_ptr, usa , altrimenti mse::TRefCountingPointerè più ottimale.

mse::TScopeOwnerPointerè simile a boost::scoped_ptr, ma funziona insieme mse::TScopeFixedPointera una relazione puntatore "forte-debole" tipo std::shared_ptre std::weak_ptr.

mse::TScopeFixedPointerpunta agli oggetti allocati nello stack o il cui puntatore "proprietario" è allocato nello stack. È (intenzionalmente) limitato nella sua funzionalità per migliorare la sicurezza in fase di compilazione senza costi di runtime. Lo scopo dei puntatori di "ambito" è essenzialmente quello di identificare un insieme di circostanze che sono abbastanza semplici e deterministiche da non richiedere alcun meccanismo di sicurezza (runtime).

mse::TRegisteredPointersi comporta come un puntatore non elaborato, tranne per il fatto che il suo valore viene impostato automaticamente su null_ptr quando l'oggetto di destinazione viene distrutto. Può essere utilizzato come sostituto generale dei puntatori non elaborati nella maggior parte delle situazioni. Come un puntatore grezzo, non ha alcuna sicurezza intrinseca del thread. Ma in cambio non ha problemi a individuare gli oggetti allocati nello stack (e ottenere il corrispondente vantaggio in termini di prestazioni). Quando i controlli in fase di esecuzione sono abilitati, questo puntatore è protetto dall'accesso alla memoria non valida. Poiché mse::TRegisteredPointerha lo stesso comportamento di un puntatore non elaborato quando punta a oggetti validi, può essere "disabilitato" (automaticamente sostituito con il corrispondente puntatore non elaborato) con una direttiva in fase di compilazione, consentendo di utilizzarlo per rilevare bug in debug / test / modalità beta senza sostenere costi generali in modalità di rilascio.

Ecco un articolo che descrive perché e come usarli. (Nota, spina spudorata.)

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.