2010-05-10 21 views

risposta

52

In sostanza, shared_ptr ha due puntatori: un puntatore per l'oggetto condiviso e un puntatore a una struttura contenente due conteggi di riferimento: uno per "i riferimenti forti", o riferimenti che hanno la proprietà, e uno per "riferimenti deboli", o riferimenti che non hanno proprietà.

Quando si copia uno shared_ptr, il costruttore di copie incrementa il conteggio dei riferimenti forti. Quando si distrugge un shared_ptr, il distruttore decrementa il conteggio dei riferimenti forti e verifica se il conteggio dei riferimenti è zero; se lo è, il distruttore cancella l'oggetto condiviso perché non c'è più nessun punto shared_ptr.

Il numero di riferimento debole viene utilizzato per supportare weak_ptr; in pratica, ogni volta che viene creato uno weak_ptr da shared_ptr, il conteggio di riferimento debole viene incrementato e ogni volta che viene distrutto il conteggio di riferimento debole viene decrementato. Finché il conteggio dei riferimenti forti o il conteggio dei riferimenti deboli è maggiore di zero, la struttura del conteggio dei riferimenti non verrà distrutta.

In effetti, finché il conteggio dei riferimenti forti è maggiore di zero, l'oggetto condiviso non verrà eliminato. Finché il conteggio dei riferimenti forti o il conteggio dei riferimenti deboli non è zero, la struttura del conteggio dei riferimenti non verrà eliminata.

+0

s/Finché il conteggio dei riferimenti deboli è maggiore/Fintanto che il conteggio dei riferimenti è maggiore /, ovviamente. – MSalters

+0

@MSalters: Grazie. –

+1

ottima risposta ... Ho familiarità con il modo in cui i puntatori intelligenti funzionano in generale, ma non ho mai conosciuto le specifiche di shared_ptr – Polaris878

0

Hanno un conteggio di riferimento interno che viene incrementato nell'operatore di costruzione/assegnazione copia shared_ptr e decrementato nel distruttore. Quando il conteggio raggiunge lo zero, il puntatore trattenuto viene cancellato.

Ecco la libreria Boost documentation per i puntatori intelligenti. Penso che l'implementazione del TR1 sia la stessa di boost::shared_ptr.

0

"Il puntatore condiviso è un puntatore intelligente (un oggetto C++ con operatore sovraccarico *() e operatore ->()) che mantiene un puntatore a un oggetto e un puntatore a un conteggio di riferimento condiviso. il puntatore intelligente viene creato utilizzando il costruttore di copie, il conteggio dei riferimenti viene incrementato Quando un puntatore condiviso viene distrutto, il conteggio dei riferimenti per l'oggetto viene decrementato. I puntatori condivisi creati dai puntatori non elaborati inizialmente hanno un conteggio di riferimento 1. Quando il conteggio dei riferimenti raggiunge 0, l'oggetto appuntito viene distrutto e la memoria che occupa viene liberata. Non è necessario distruggere esplicitamente gli oggetti: verrà eseguito automaticamente quando viene eseguito il distruttore dell'ultimo puntatore. " Da here.

10

Esistono almeno tre meccanismi ben noti.

contatori esterni

Quando viene creato il primo puntatore condiviso in un oggetto, viene creato un oggetto conteggio di riferimento separato e inizializzato a 1. Quando il puntatore viene copiato, il contatore di riferimento viene aumentata; quando un puntatore viene distrutto viene diminuito. L'assegnazione del puntatore aumenta di un conteggio e ne riduce un altro (in questo ordine, altrimenti l'autoassegnazione ptr=ptr si interromperà). Se il conteggio dei riferimenti raggiunge lo zero, non esistono più puntatori e l'oggetto viene eliminato.

Contatori interni

Un contatore interno richiede che l'oggetto puntato ha un campo contatore. Questo è solitamente ottenuto derivando da una classe base specifica.In cambio, questo consente di risparmiare un mucchio di allocazione del conteggio di riferimento, e permette ripetuta creazione di puntatori condivisi dal puntatori prime (con i contatori esterni, che ci si finisce con due conteggi per un oggetto)

collegamenti circolari

Invece di utilizzare un contatore, è possibile mantenere tutti i puntatori condivisi su un oggetto in un grafico circolare. Il primo puntatore creato punta a se stesso. Quando copi un puntatore, inserisci la copia nella cerchia. Quando lo elimini, lo rimuovi dalla cerchia. Ma quando il puntatore distrutto punta a se stesso, cioè quando è l'unico puntatore, si cancella l'oggetto puntato.

Lo svantaggio è che la rimozione di un nodo da un elenco circolare a collegamento singolo è piuttosto costosa in quanto è necessario iterare su tutti i nodi per trovare il predecessore. Questo può essere particolarmente doloroso a causa della scarsa localizzazione di riferimento.

Variazioni

il 2 e 3 possono essere combinati idea: la classe di base può essere parte di tale grafico circolare, invece di contenere un conteggio. Ovviamente, questo significa che l'oggetto può essere cancellato solo quando punta a se stesso (lunghezza del ciclo 1, nessun puntatore rimanente ad esso). Di nuovo, il vantaggio è che puoi creare puntatori intelligenti da puntatori deboli, ma le scarse prestazioni dell'eliminazione di un puntatore dalla catena rimangono un problema.

L'esatta struttura del grafico per l'idea 3 non ha importanza. È anche possibile creare una struttura ad albero binaria, con l'oggetto appuntito nella radice. Di nuovo, l'operazione difficile sta rimuovendo un nodo puntatore condiviso da quel grafico. Il vantaggio è che se si hanno molti indicatori su molti thread, la crescita di una parte del grafico non è un'operazione molto contesa.

+0

'boost :: shared_ptr' usa l'approccio' Contatori Esterni '. C'è un modo per implementare weak_ptr con l'approccio 'Counters interni '(senza riscrivere la terra)? – Pavel

+0

@Pavel: Sì, ma è piuttosto complesso. Avrai bisogno di un operatore '' new new '' e in particolare della sua corrispondenza 'operator delete' nella classe base. Si usa questo per creare una "lapide" dove una volta si trovava la classe base, in modo che 'weak_ptr' continui a puntare a quella lapide. – MSalters

14

Sono generalmente d'accordo con la risposta di James McNellis. Tuttavia c'è un altro punto che dovrebbe essere menzionato.

Come forse sapete, shared_ptr<T> può essere utilizzato anche quando il tipo T non è completamente definito.

Cioè:

class AbraCadabra; 

boost::shared_ptr<AbraCadabra> myPtr; 
// ... 

Questo compilerà & lavoro. A differenza di molte altre implementazioni di puntatori intelligenti, che in realtà richiedono che il tipo incapsulato sia completamente definito per poter essere utilizzato. Questo è legato al fatto che il puntatore intelligente dovrebbe sapere di eliminare l'oggetto incapsulato quando non è più referenziato, e per eliminare un oggetto uno deve sapere cosa è.

Ciò si ottiene il seguente trucco: shared_ptr realtà costituito dai seguenti:

  1. Un puntatore opaco all'oggetto
  2. contatori di riferimento comune (quello che James McNellis descritto)
  3. Un puntatore al assegnato factory che sa come distruggere il tuo oggetto.

Il factory di cui sopra è un oggetto helper con una singola funzione virtuale, che dovrebbe eliminare il tuo oggetto in modo corretto.

Questo factory viene effettivamente creato quando si imposta sul puntatore condiviso.

Cioè, il seguente codice

AbraCadabra* pObj = /* get it from somewhere */; 
myPtr.reset(pObj); 

Questo è dove questa fabbrica è allocato. Nota: la funzione reset è in realtà una funzione modello. In realtà crea la factory per il tipo specificato (tipo dell'oggetto passato come parametro). Qui è dove il tuo tipo dovrebbe essere completamente definito. Cioè, se non è ancora definito, otterrai un errore di compilazione.

Nota: se si crea effettivamente un oggetto di un tipo derivato (derivato da AbraCadabra) e lo si assegna allo shared_ptr, verrà eliminato in modo corretto anche se il distruttore non è virtuale. Il shared_ptr eliminerà sempre l'oggetto in base al tipo visualizzato nella funzione reset.

Quindi shared_ptr è una variante piuttosto sofisticata di un puntatore intelligente. Offre una straordinaria flessibilità . Tuttavia, dovresti sapere che questa flessibilità ha un prezzo di una prestazione estremamente negativa rispetto ad altre possibili implementazioni del puntatore intelligente.

D'altro canto, ci sono i cosiddetti puntatori intelligenti "intrusivi". Non hanno tutta quella flessibilità, ma al contrario danno le migliori prestazioni.

A favore di shared_ptr rispetto ai puntatori intelligenti inrusive:

  • utilizzo molto flessibile. È necessario definire il tipo incapsulato solo assegnandolo allo shared_ptr. Questo è molto utile per i grandi progetti, riduce notevolmente le dipendenze.
  • Il tipo incapsulato non deve avere un distruttore virtuale, i tipi ancora polimorfici verranno eliminati correttamente.
  • Può essere utilizzato con puntatori deboli.

Contro di shared_ptr rispetto ai puntatori intelligenti inrusive:

  1. performance molto barbara e spreco di memoria heap. Nell'assegnazione vengono assegnati altri 2 oggetti: contatori di riferimento, oltre alla fabbrica (spreco di memoria, lento). Ciò tuttavia accade solo su reset. Quando uno shared_ptr è assegnato a un altro - non viene assegnato altro.
  2. Quanto sopra può generare un'eccezione. (condizione di esaurimento della memoria). Al contrario, i puntatori intelligenti intrusivi non possono mai lanciare (a parte le eccezioni del processo relative all'accesso di memoria non valido, allo stack overflow ecc.)
  3. Anche la cancellazione dell'oggetto è lenta: è necessario deallocare altre due strutture.
  4. Quando si lavora con puntatori intelligenti intrusivi, è possibile mescolare liberamente i puntatori intelligenti con quelli grezzi. Questo è ok perché il conteggio dei riferimenti effettivo risiede all'interno dell'oggetto stesso, che è singolo. Al contrario - con shared_ptr è possibile non mescolare con puntatori grezzi.

    AbraCadabra * pObj =/* ottenere da qualche parte * /; myPtr.reset (pObj); // ... pObj = myPtr.ottenere(); boost :: shared_ptr myPtr2 (pObj); // oops

Quanto sopra si bloccherà.

+0

Hai menzionato weak_ptr come vantaggio del design shared_ptr. Non è possibile avere weak_ptr con ptr intrusivo? O implicherebbe reinventare shared_ptr insieme a ptr intrusivo? – Pavel