Puntatori intelligenti: chi possiede l'oggetto? [chiuso]


114

Il C ++ riguarda la proprietà della memoria, ovvero la semantica della proprietà .

È responsabilità del proprietario di una porzione di memoria allocata dinamicamente rilasciare quella memoria. Quindi la domanda diventa davvero chi possiede la memoria.

In C ++ proprietà è documentata dal tipo di greggio puntatore è avvolto all'interno così una buona (IMO) C ++ programma è molto raro ( raro , non mai ) per vedere puntatori prime passate muoversi (come puntatori prime non hanno dedotto proprietà così possiamo non dire chi possiede la memoria e quindi senza un'attenta lettura della documentazione non si può dire chi è responsabile della proprietà).

Al contrario, è raro vedere puntatori non elaborati archiviati in una classe, ogni puntatore non elaborato è archiviato all'interno del proprio wrapper di puntatori intelligenti. ( NB: Se non possiedi un oggetto, non dovresti conservarlo perché non puoi sapere quando uscirà dall'ambito e verrà distrutto.)

Quindi la domanda:

  • Che tipo di semantica di proprietà hanno incontrato le persone?
  • Quali classi standard vengono utilizzate per implementare quelle semantiche?
  • In quali situazioni le trovi utili?

Consente di mantenere 1 tipo di proprietà semantica per risposta in modo che possano essere votate su e giù individualmente.

Sommario:

Concettualmente, i puntatori intelligenti sono semplici e un'implementazione ingenua è facile. Ho visto molti tentativi di implementazione, ma invariabilmente sono rotti in un modo che non è ovvio per un uso casuale ed esempi. Quindi consiglio sempre di utilizzare puntatori intelligenti ben collaudati da una libreria piuttosto che rotolare i tuoi. std::auto_ptro uno dei puntatori intelligenti Boost sembra coprire tutte le mie esigenze.

std::auto_ptr<T>:

Una sola persona possiede l'oggetto. È consentito il trasferimento di proprietà.

Utilizzo: consente di definire interfacce che mostrano il trasferimento esplicito della proprietà.

boost::scoped_ptr<T>

Una sola persona possiede l'oggetto. Il trasferimento di proprietà NON è consentito.

Utilizzo: utilizzato per mostrare la proprietà esplicita. L'oggetto verrà distrutto dal distruttore o se ripristinato in modo esplicito.

boost::shared_ptr<T>( std::tr1::shared_ptr<T>)

Proprietà multipla. Questo è un semplice puntatore conteggio dei riferimenti. Quando il conteggio dei riferimenti raggiunge lo zero, l'oggetto viene distrutto.

Utilizzo: quando un oggetto può avere più fiori con una durata che non può essere determinata in fase di compilazione.

boost::weak_ptr<T>:

Utilizzato shared_ptr<T>in situazioni in cui può verificarsi un ciclo di puntatori.

Utilizzo: utilizzato per impedire ai cicli di conservare gli oggetti quando solo il ciclo mantiene un refcount condiviso.


14
?? Qual era la domanda?
Pacerier

9
Volevo solo sottolineare che da quando questa domanda è stata pubblicata auto_ptr è stata deprecata a favore di (ora standardizzato) unique_ptr
Juan Campa

In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good (IMO) Può essere riformulato? Non lo capisco affatto.
lolololol del

@lololololol Hai tagliato a metà la frase. In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good C++ program it is very rare to see RAW pointers passed around. I puntatori RAW non hanno la semantica della proprietà. Se non conosci il proprietario non sai chi è responsabile dell'eliminazione dell'oggetto. Esistono diverse classi standard che vengono utilizzate per avvolgere i puntatori (std :: shared_ptr, std :: unique_ptr ecc) che definiscono la proprietà e quindi definire chi è responsabile dell'eliminazione del puntatore.
Martin York,

1
In C ++ 11 + NON USARE auto_ptr! Usa invece unique_ptr!
val dice Reinstate Monica

Risposte:


20

Per me, questi 3 tipi coprono la maggior parte delle mie esigenze:

shared_ptr - conteggio dei riferimenti, deallocazione quando il contatore raggiunge lo zero

weak_ptr- come sopra, ma è uno "schiavo" per un shared_ptr, non può deallocare

auto_ptr- quando la creazione e la deallocazione avvengono all'interno della stessa funzione, oppure quando l'oggetto deve essere considerato un solo proprietario sempre. Quando assegni un puntatore a un altro, il secondo "ruba" l'oggetto dal primo.

Ho la mia implementazione per questi, ma sono disponibili anche in Boost.

Passo ancora gli oggetti per riferimento ( constquando possibile), in questo caso il metodo chiamato deve assumere che l'oggetto sia vivo solo durante il tempo della chiamata.

C'è un altro tipo di puntatore che uso che chiamo hub_ptr . È quando hai un oggetto che deve essere accessibile da oggetti annidati in esso (di solito come una classe base virtuale). Questo potrebbe essere risolto passando weak_ptra loro, ma non ha shared_ptra se stesso. Poiché sa che questi oggetti non vivrebbero più a lungo di lui, passa loro un hub_ptr (è solo un wrapper di modello per un normale puntatore).


2
Invece di creare la tua classe di puntatore (hub_ptr), perché non lo passi * a questi oggetti e lasci che lo memorizzino come riferimento? Dal momento che ammetti persino che gli oggetti verranno distrutti contemporaneamente alla classe del proprietario, non capisco il punto di saltare attraverso così tanti cerchi.
Michel

4
È fondamentalmente un contratto di progettazione per rendere le cose chiare. Quando l'oggetto figlio riceve hub_ptr, sa che l'oggetto appuntito non verrà distrutto durante la vita del bambino e non ha alcuna proprietà su di esso. Sia gli oggetti contenuti che quelli contenitore concordano con un chiaro insieme di regole. Se usi un puntatore nudo, le regole possono essere documentate, ma non verranno applicate dal compilatore e dal codice.
Fabio Ceconello

1
Nota anche che puoi avere #ifdefs per fare in modo che hub_ptr sia typedef su un puntatore nudo nelle build di rilascio, quindi l'overhead esisterà solo nella build di debug.
Fabio Ceconello

3
Tieni presente che la documentazione di Boost contraddice la tua descrizione di scoped_ptr. Afferma che lo è noncopyablee che la proprietà non può essere trasferita.
Alec Thomas

3
@ Alec Thomas, hai ragione. Stavo pensando ad auto_ptr e ho scritto scoped_ptr. Corretto.
Fabio Ceconello

23

Modello C ++ semplice

Nella maggior parte dei moduli che ho visto, per impostazione predefinita, si presumeva che la ricezione dei puntatori non fosse la proprietà. In effetti, funzioni / metodi che abbandonano la proprietà di un puntatore erano molto rari e lo esprimevano esplicitamente nella loro documentazione.

Questo modello presuppone che l'utente sia proprietario solo di ciò che alloca esplicitamente . Tutto il resto viene automaticamente eliminato (all'uscita dall'ambito o tramite RAII). Questo è un modello simile al C, esteso dal fatto che la maggior parte dei puntatori sono di proprietà di oggetti che li deallocano automaticamente o quando necessario (alla distruzione di detti oggetti, principalmente), e che la durata della vita degli oggetti è prevedibile (RAII è tuo amico, ancora).

In questo modello, i puntatori grezzi circolano liberamente e per lo più non pericolosi (ma se lo sviluppatore è abbastanza intelligente, utilizzerà invece riferimenti ogni volta che è possibile).

  • puntatori grezzi
  • std :: auto_ptr
  • boost :: scoped_ptr

Modello C ++ a punta intelligente

In un codice pieno di puntatori intelligenti, l'utente può sperare di ignorare la durata degli oggetti. Il proprietario non è mai il codice utente: è il puntatore intelligente stesso (RAII, di nuovo). Il problema è che i riferimenti circolari mescolati con puntatori intelligenti con conteggio dei riferimenti possono essere mortali , quindi devi gestire sia i puntatori condivisi che i puntatori deboli. Quindi devi ancora considerare la proprietà (il puntatore debole potrebbe non puntare a nulla, anche se il suo vantaggio rispetto al puntatore grezzo è che può dirtelo).

  • boost :: shared_ptr
  • boost :: weak_ptr

Conclusione

Indipendentemente dai modelli che descrivo, salvo eccezioni, ricevere un puntatore non significa riceverne la proprietà ed è comunque molto importante sapere chi possiede chi . Anche per il codice C ++ che utilizza pesantemente riferimenti e / o puntatori intelligenti.


10

Non avere la proprietà condivisa. Se lo fai, assicurati che sia solo con il codice che non controlli.

Questo risolve il 100% dei problemi, poiché ti costringe a capire come tutto interagisce.


2
  • Proprietà condivisa
  • boost :: shared_ptr

Quando una risorsa è condivisa tra più oggetti. Il boost shared_ptr utilizza il conteggio dei riferimenti per assicurarsi che la risorsa venga de-allocata quando tutti hanno finito.


2

std::tr1::shared_ptr<Blah> è spesso la soluzione migliore.


2
shared_ptr è il più comune. Ma ce ne sono molti altri. Ognuno ha il proprio modello di utilizzo e luoghi buoni e cattivi da citare in giudizio. Un po 'più di descrizione sarebbe carino.
Martin York

Se sei bloccato con un compilatore precedente, boost :: shared_ptr <blah> è ciò su cui si basa std :: tr1 :: shared_ptr <blah>. È una classe abbastanza semplice che puoi probabilmente estrarla da Boost e usarla anche se il tuo compilatore non è supportato dall'ultima versione di Boost.
Branan

2

Da boost, c'è anche la libreria del contenitore di puntatori . Questi sono un po 'più efficienti e più facili da usare di un contenitore standard di puntatori intelligenti, se utilizzerai gli oggetti solo nel contesto del loro contenitore.

Su Windows, ci sono i puntatori COM (IUnknown, IDispatch e friends) e vari puntatori intelligenti per gestirli (ad esempio CComPtr di ATL e i puntatori intelligenti generati automaticamente dall'istruzione "import" in Visual Studio in base alla classe _com_ptr ).


1
  • Un proprietario
  • boost :: scoped_ptr

Quando è necessario allocare la memoria in modo dinamico ma si desidera essere sicuri che venga deallocata su ogni punto di uscita del blocco.

Lo trovo utile in quanto può essere facilmente riposizionato e rilasciato senza doversi preoccupare di una perdita


1

Non credo di essere mai stato in grado di condividere la proprietà del mio progetto. In effetti, dall'alto della mia testa l'unico caso valido a cui riesco a pensare è il modello Flyweight.


1

yasper :: ptr è un'alternativa leggera, simile a boost :: shared_ptr. Funziona bene nel mio (per ora) piccolo progetto.

Nella pagina web all'indirizzo http://yasper.sourceforge.net/ è descritto come segue:

Perché scrivere un altro puntatore intelligente C ++? Esistono già diverse implementazioni di puntatori intelligenti di alta qualità per C ++, in particolare il pantheon dei puntatori Boost e SmartPtr di Loki. Per un buon confronto delle implementazioni di puntatori intelligenti e quando il loro uso è appropriato, leggere The New C ++: Smart (er) Pointers di Herb Sutter. In contrasto con le ampie funzionalità di altre librerie, Yasper è un puntatore per il conteggio dei riferimenti strettamente focalizzato. Corrisponde strettamente alle policy shared_ptr di Boost e RefCounted / AllowConversion di Loki. Yasper consente ai programmatori C ++ di dimenticare la gestione della memoria senza introdurre le grandi dipendenze di Boost o dover apprendere i complicati modelli di criteri di Loki. Filosofia

* small (contained in single header)
* simple (nothing fancy in the code, easy to understand)
* maximum compatibility (drop in replacement for dumb pointers)

L'ultimo punto può essere pericoloso, poiché yasper consente azioni rischiose (ma utili) (come l'assegnazione a puntatori non elaborati e il rilascio manuale) non consentite da altre implementazioni. Fai attenzione, usa queste funzionalità solo se sai cosa stai facendo!


1

Esiste un'altra forma usata di frequente di proprietario trasferibile singolo, ed è preferibile auto_ptrperché evita i problemi causati dalla auto_ptrfolle corruzione della semantica dell'assegnazione.

Non parlo di nientemeno che swap. Qualsiasi tipo con una swapfunzione adeguata può essere concepito come un riferimento intelligente a un contenuto, che possiede fino a quando la proprietà non viene trasferita a un'altra istanza dello stesso tipo, scambiandoli. Ogni istanza conserva la propria identità ma viene associata a nuovi contenuti. È come un riferimento riassicurabile in modo sicuro.

(È un riferimento intelligente piuttosto che un puntatore intelligente perché non è necessario dereferenziarlo esplicitamente per ottenere il contenuto.)

Ciò significa che auto_ptr diventa meno necessario: è necessario solo per colmare le lacune in cui i tipi non hanno una buona swapfunzione. Ma tutti i contenitori std lo fanno.


Forse diventa meno necessario (direi che scoped_ptr lo rende meno necessario di così), ma non sta andando via. Avere una funzione di scambio non ti aiuta affatto se allochi qualcosa nell'heap e qualcuno lancia prima di eliminarlo, o semplicemente dimentichi.
Michel

È esattamente quello che ho detto nell'ultimo paragrafo.
Daniel Earwicker,

0
  • Un proprietario: Aka elimina in copia
  • std :: auto_ptr

Quando il creatore dell'oggetto vuole cedere esplicitamente la proprietà a qualcun altro. Questo è anche un modo per documentare il codice che ti sto dando e non lo sto più monitorando, quindi assicurati di eliminarlo quando hai finito.

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.