2009-12-15 22 views
20

Sono venuto a sapere che puntatore intelligente viene utilizzato per la gestione delle risorse e supporta RAII.pro e contro di puntatori intelligenti

Ma quali sono i casi in cui il puntatore intelligente non sembra intelligente e le cose da tenere a mente durante l'utilizzo?

+0

Ya I am, non hai visto mio-serie MI. – Ashish

risposta

18

puntatori intelligenti non aiutano contro i loop in strutture grafico-like.

Ad esempio, l'oggetto A contiene un puntatore intelligente all'oggetto B e l'oggetto B - torna all'oggetto A. Se si rilasciano tutti i puntatori a entrambi A e B prima della disconnessione A da B (o B da A) sia A che B si terranno l'un l'altro e formeranno una felice perdita di memoria.

La raccolta di dati inutili potrebbe aiutare contro questo: potrebbe vedere che entrambi gli oggetti sono irraggiungibili e liberarli.

+16

* Forti * puntatori intelligenti nei loop causeranno perdite di memoria, ma l'uso attento dei puntatori * deboli * (una parte di qualsiasi libreria completa di puntatori intelligenti) consente di utilizzare efficacemente i puntatori intelligenti senza perdite anche con strutture circolari. –

+1

questo è anche correlato a una perdita comune con GC, quando si dispone di una struttura circolare e non è più necessaria una parte di esso è necessario interrompere manualmente il ciclo per consentire GC (o consentire a GC di raccogliere l'intera struttura) ad es. C# events –

+3

@jk: Non proprio la stessa cosa. Un GC può gestire il caso in cui A punta a B e B punta A senza che nessun altro oggetto punti a nessuno degli oggetti. Devi solo "interrompere il ciclo" se c'è ancora un altro riferimento a A o B. Quindi non è proprio il ciclo il problema. Un GC mezzo decente può gestirli. Il problema è quando crei riferimenti da dati usati raramente a dati di lunga durata, senza accorgertene (come negli eventi). Non ha niente a che fare con i cicli. – jalf

1

Ecco alcune cose

  • Non c'è nessuna entità definitiva che distrugge l'oggetto. Spesso si desidera sapere esattamente quando e come un oggetto viene distrutto.
  • Dipendenze circolari: se esistono, si verificherà una perdita di memoria. Questo di solito è dove weak_ptr venire in soccorso.
+1

Di solito è sufficiente sapere che un oggetto è stato distrutto, più o meno al momento opportuno. Non ho incontrato molti casi in cui avevo bisogno di sapere esattamente quando e in risposta a cosa. –

+2

La distruzione dell'oggetto è garantita in un luogo ben definito (anche nel caso di puntatori intelligenti conteggiati con ref il fatto che la distruzione avvenga in un punto ben definito, è solo quello con puntatori intelligenti conteggiati con riferimento non si conosce quel posto fino al runtime). –

+0

"_Così di solito dove weak_ptr viene in soccorso._" di solito no, 'weak_ptr' può solo nascondere il problema. – curiousguy

6

Questo è piuttosto interessante: Smart Pointers.
È un capitolo di esempio di "Modern C++ Design" di Andrei Alexandrescu.

0

Raymond Chen è notoriamente ambivalente riguardo ai puntatori intelligenti. Esistono problemi relativi allo when the destructor actually runs (si noti che il distruttore viene eseguito in un tempo ben definito in un ordine ben definito: è solo che una volta ogni tanto si dimentica che è dopo l' l'ultima riga della funzione).

Ricorda inoltre che "puntatore intelligente" è una categoria piuttosto grande. Includo std::vector in quella categoria (a std::vector is essentially a smart array).

+4

No, std :: vector non è uno smart array. copiare un vettore duplica l'array !!! –

+1

gestisce la propria memoria anche se –

+6

Ma qualcosa che "gestisce la propria memoria è solo una" classe C++ sana "e * niente * di più.È il minimo che dovresti aspettarti da * qualsiasi * classe. Un puntatore intelligente è quello che gestisce la memoria * di altre persone *. Uno a cui viene dato un puntatore e si occupa di eliminarlo al momento giusto. E la cosa del distruttore non ha nulla a che fare con i puntatori intelligenti. È una cosa generale in C++, e ogni programmatore C++ dovrebbe conoscerlo meglio a memoria. – jalf

3

Oltre alle limitazioni tecniche (già menzionate: dipendenze circolari), direi che la cosa più importante sui puntatori intelligenti è ricordare che è ancora una soluzione alternativa per eliminare gli oggetti allocati nell'heap. L'allocazione dello stack è l'opzione migliore per la maggior parte dei casi, insieme all'utilizzo di riferimenti, per gestire la durata degli oggetti.

+0

L'allocazione dello stack non è sicuramente l'opzione migliore nella maggior parte dei casi. L'allocazione dello stack ha una sua limitazione e li colpisci abbastanza velocemente se gestisci un numero ragionevole di strutture di dati. I programmi più complessi usano l'allocazione dell'heap semplicemente perché fai esplodere lo stack abbastanza velocemente quando i numeri crescono. Linux default è 8megs pr thread IIRC. Sulla maggior parte dei sistemi embedded è nella gamma kb. –

+0

Vedo il tuo punto. Ma preferiresti piuttosto creare classi di contenitori specifici che allocano e deallocano i propri blocchi di memoria piuttosto che allocare blocchi di memoria ovunque nel tuo codice e sperare di non aver dimenticato nessuno? In questo modo, puoi eludere il problema dello stack-size ... –

+0

Certo che siamo d'accordo su questo - questo è ciò che è buono per C++ a mio avviso :) Stavo solo discutendo contro il tuo punto che "l'assegnazione dello stack è l'opzione migliore per la maggior parte casi". –

10

Vorrei menzionare i limiti delle prestazioni. I puntatori intelligenti solitamente utilizzano operazioni atomiche (come InterlockedIncrement in API Win32) per il conteggio dei riferimenti. Queste funzioni sono molto più lente dell'aritmetica dei numeri interi.

Nella maggior parte dei casi, questa penalizzazione delle prestazioni non è un problema, ma assicurati di non eseguire troppe copie non necessarie dell'oggetto puntatore intelligente (preferisci passare il puntatore intelligente come riferimento nelle chiamate alle funzioni).

+1

A meno che la penalità delle prestazioni non diventi ovvia, sconsiglio di usare riferimenti a puntatori intelligenti. Se usato come argomento di funzione, non ha alcun impatto negativo, ma può essere usato come membro della classe. Attenzione! –

+1

@Benoit: Sono abbastanza sicuro che "passare il puntatore intelligente per riferimento" significa in particolare nelle chiamate, nel qual caso non dovresti davvero consigliare a qualcuno di passarlo per valore. –

+2

Sì, volevo dire passarlo come argomento a una funzione. Ho modificato la risposta per renderla più chiara. –

0

C'è un problema con il conteggio dei riferimenti in alcuni tipi di strutture di dati che hanno cicli. Possono anche esserci problemi con l'accesso ai puntatori intelligenti da più thread, l'accesso simultaneo ai conteggi di riferimento può causare problemi. C'è un programma di utilità in boost chiamato atomic.hpp che può mitigare questo problema.

0

Molte persone si imbattono in problemi quando utilizzano puntatori intelligenti mescolati con puntatori grezzi (sugli stessi oggetti). Un tipico esempio è quando si interagisce con un'API che utilizza puntatori grezzi.
Ad esempio; in boost::shared_ptr c'è una funzione .get() che restituisce il puntatore raw. Buona funzionalità se usata con cura, ma molte persone sembrano inciamparci.
IMHO è un esempio di "astrazione che perde".

5

Controllare le transizioni - durante l'assegnazione tra puntatori grezzi e intelligenti. I cattivi puntatori intelligenti - come _com_ptr_t - peggiorano le cose consentendo conversioni implicite. La maggior parte degli errori si verifica durante la transizione.

Attenzione per cicli - come già detto, sono necessari indicatori deboli per interrompere i cicli. Tuttavia, in un grafico complesso che non è sempre facile da fare.

Troppa scelta - la maggior parte delle librerie offre diverse implementazioni con diversi vantaggi/svantaggi. Sfortunatamente, la maggior parte delle volte queste diverse varianti non sono compatibili, il che diventa un problema quando si mischiano le librerie. (per esempio, LibA usa LOKI, LibB usa boost). Dovendo pianificare in anticipo per enable_shared_from_this fa schifo, dover decidere convenzioni di denominazione tra intrusive_ptr, shared_ptr e weak_ptr per un sacco di oggetti fa schifo.


Per me, il singolo più e vantaggio shared_ptr (o una funzionalità simile) è che è accoppiato alla sua distruzione politica alla creazione. Sia C++ che Win32 offrono così tanti modi per sbarazzarsi di cose che non è nemmeno divertente. L'accoppiamento al momento della costruzione (senza influenzare il tipo effettivo del puntatore) significa che ho entrambe le politiche in un unico posto.