spiegazione dei puntatori intelligenti (boost)


220

Qual è la differenza tra il seguente set di puntatori? Quando usi ogni puntatore nel codice di produzione, se non del tutto?

Gli esempi sarebbero apprezzati!

  1. scoped_ptr

  2. shared_ptr

  3. weak_ptr

  4. intrusive_ptr

Usi boost nel codice di produzione?

Risposte:


339

Proprietà di base dei puntatori intelligenti

È facile quando si dispone di proprietà che è possibile assegnare a ciascun puntatore intelligente. Esistono tre proprietà importanti.

  • nessuna proprietà
  • trasferimento di proprietà
  • quota di proprietà

Il primo significa che un puntatore intelligente non può eliminare l'oggetto, perché non lo possiede. Il secondo significa che solo un puntatore intelligente può mai puntare allo stesso oggetto contemporaneamente. Se il puntatore intelligente deve essere restituito dalle funzioni, ad esempio, la proprietà viene trasferita al puntatore intelligente restituito.

Il terzo significa che più puntatori intelligenti possono puntare allo stesso oggetto contemporaneamente. Questo vale anche per un puntatore non elaborato, tuttavia i puntatori non elaborati mancano di una funzione importante: non definiscono se possiedono o meno. Una condivisione di proprietà del puntatore intelligente eliminerà l'oggetto se ogni proprietario rinuncia all'oggetto. Questo comportamento sembra essere necessario spesso, quindi i puntatori intelligenti di proprietà condivisa sono ampiamente diffusi.

Alcuni proprietari di puntatori intelligenti non supportano né il secondo né il terzo. Non possono quindi essere restituiti da funzioni o passati altrove. Per quale è più adattoRAII scopi in cui il puntatore intelligente viene mantenuto locale ed è appena creato in modo da liberare un oggetto dopo che è uscito dall'ambito.

La quota di proprietà può essere implementata con un costruttore di copie. Questo naturalmente copia un puntatore intelligente e sia la copia che l'originale faranno riferimento allo stesso oggetto. Il trasferimento di proprietà al momento non può essere realmente implementato in C ++, poiché non vi sono mezzi per trasferire qualcosa da un oggetto a un altro supportato dalla lingua: se si tenta di restituire un oggetto da una funzione, ciò che sta accadendo è che l'oggetto viene copiato. Quindi un puntatore intelligente che implementa il trasferimento di proprietà deve usare il costruttore di copie per implementare quel trasferimento di proprietà. Tuttavia, questo a sua volta interrompe il suo utilizzo in contenitori, poiché i requisiti stabiliscono un determinato comportamento del costruttore di copie di elementi di contenitori che è incompatibile con il cosiddetto comportamento "costruttore mobile" di questi puntatori intelligenti.

C ++ 1x fornisce supporto nativo per il trasferimento di proprietà introducendo i cosiddetti "costruttori di spostamento" e "operatori di assegnazione di spostamenti". Viene fornito anche con un puntatore intelligente di trasferimento di proprietà chiamato unique_ptr.

Classificare i puntatori intelligenti

scoped_ptrè un puntatore intelligente che non è né trasferibile né condivisibile. È utilizzabile solo se è necessario localmente allocare memoria, ma assicurarsi che venga liberato di nuovo quando non rientra nell'ambito. Ma può ancora essere scambiato con un altro scoped_ptr, se lo si desidera.

shared_ptrè un puntatore intelligente che condivide la proprietà (terzo tipo sopra). Viene contato come riferimento in modo che possa vedere quando l'ultima copia di esso esce dall'ambito e quindi libera l'oggetto gestito.

weak_ptrè un puntatore intelligente non proprietario. Viene utilizzato per fare riferimento a un oggetto gestito (gestito da shared_ptr) senza aggiungere un conteggio dei riferimenti. Normalmente, è necessario estrarre il puntatore non elaborato da shared_ptr e copiarlo. Ma questo non sarebbe sicuro, in quanto non avresti un modo per verificare quando l'oggetto è stato effettivamente eliminato. Quindi, weak_ptr fornisce mezzi facendo riferimento a un oggetto gestito da shared_ptr. Se è necessario accedere all'oggetto, è possibile bloccarne la gestione (per evitare che in un altro thread un shared_ptr lo liberi mentre si utilizza l'oggetto) e quindi utilizzarlo. Se weak_ptr punta a un oggetto già eliminato, ti noterà lanciando un'eccezione. L'uso di weak_ptr è molto utile quando si ha un riferimento ciclico: il conteggio dei riferimenti non può facilmente far fronte a una situazione del genere.

intrusive_ptrè simile a shared_ptr ma non mantiene il conteggio dei riferimenti in shared_ptr ma lascia incrementare / decrementare il conteggio per alcune funzioni di supporto che devono essere definite dall'oggetto gestito. Ciò ha il vantaggio che un oggetto già referenziato (che ha un conteggio dei riferimenti incrementato da un meccanismo di conteggio dei riferimenti esterno) può essere inserito in un intrusive_ptr - perché il conteggio dei riferimenti non è più interno al puntatore intelligente, ma il puntatore intelligente utilizza un esistente meccanismo di conteggio dei riferimenti.

unique_ptrè un trasferimento del puntatore di proprietà. Non puoi copiarlo, ma puoi spostarlo usando i costruttori di spostamento di C ++ 1x:

unique_ptr<type> p(new type);
unique_ptr<type> q(p); // not legal!
unique_ptr<type> r(move(p)); // legal. p is now empty, but r owns the object
unique_ptr<type> s(function_returning_a_unique_ptr()); // legal!

Questa è la semantica a cui obbedisce std :: auto_ptr, ma a causa della mancanza del supporto nativo per lo spostamento, non riesce a fornirli senza insidie. unique_ptr ruberà automaticamente risorse da un altro unique_ptr temporaneo che è una delle caratteristiche chiave della semantica di spostamento. auto_ptr sarà deprecato nella prossima versione C ++ Standard a favore di unique_ptr. C ++ 1x consentirà inoltre oggetti di riempimento che sono solo mobili ma non copiabili in contenitori. Quindi, per esempio, puoi inserire unique_ptr in un vettore. Mi fermo qui e ti rimando a un bell'articolo su questo se vuoi leggere di più su questo.


3
grazie per gli elogi amico. lo apprezzo quindi ora otterrai +1: p
Johannes Schaub - litb

@litb: ho dei dubbi sul "trasferimento di proprietà"; Sono d'accordo che non esiste un reale trasferimento di proprietà tra gli oggetti in C ++ 03, ma per i puntatori intelligenti non è possibile farlo, grazie al meccanismo di copia distruttiva indicato qui informit.com/articles/article.aspx?p=31529&seqNum= 5 .
legends2k

3
risposta fantastica. Nota: auto_ptrè già obsoleto (C ++ 11).
Nickolay

2
"questo a sua volta interrompe il suo utilizzo in contenitori, poiché i requisiti stabiliscono un determinato comportamento del costruttore di copie di elementi di contenitori che è incompatibile con il cosiddetto comportamento" costruttore mobile "di questi puntatori intelligenti." Non ho ottenuto quella parte.
Raja,

Mi è stato anche detto che intrusive_ptrpuò essere preferibile farlo shared_ptrper una migliore coerenza della cache. Apparentemente la cache funziona meglio se si memorizza il conteggio dei riferimenti come parte della memoria dell'oggetto gestito stesso anziché di un oggetto separato. Questo può essere implementato in un modello o in una superclasse dell'oggetto gestito.
Eliot

91

scoped_ptr è il più semplice. Quando esce dal campo di applicazione, viene distrutto. Il seguente codice è illegale (scoped_ptrs non è copiabile) ma illustrerà un punto:

std::vector< scoped_ptr<T> > tPtrVec;
{
     scoped_ptr<T> tPtr(new T());
     tPtrVec.push_back(tPtr);
     // raw T* is freed
}
tPtrVec[0]->DoSomething(); // accessing freed memory

shared_ptr è conteggiato come riferimento. Ogni volta che si verifica una copia o un'assegnazione, il conteggio dei riferimenti viene incrementato. Ogni volta che viene sparato un distruttore di un'istanza, il conteggio dei riferimenti per il T * grezzo viene diminuito. Una volta che è 0, il puntatore viene liberato.

std::vector< shared_ptr<T> > tPtrVec;
{
     shared_ptr<T> tPtr(new T());
     // This copy to tPtrVec.push_back and ultimately to the vector storage
     // causes the reference count to go from 1->2
     tPtrVec.push_back(tPtr);
     // num references to T goes from 2->1 on the destruction of tPtr
}
tPtrVec[0]->DoSomething(); // raw T* still exists, so this is safe

weak_ptr è un riferimento debole a un puntatore condiviso che richiede di verificare se il punto_partecipato_ptr è ancora in giro

std::vector< weak_ptr<T> > tPtrVec;
{
     shared_ptr<T> tPtr(new T());
     tPtrVec.push_back(tPtr);
     // num references to T goes from 1->0
}
shared_ptr<T> tPtrAccessed =  tPtrVec[0].lock();
if (tPtrAccessed[0].get() == 0)
{
     cout << "Raw T* was freed, can't access it"
}
else
{
     tPtrVec[0]->DoSomething(); // raw 
}

intrusive_ptr viene in genere utilizzato quando è presente un smart ptr di terze parti che è necessario utilizzare. Chiamerà una funzione gratuita per aggiungere e decrementare il conteggio dei riferimenti. Vedi il link per aumentare la documentazione per maggiori informazioni.


non if (tPtrAccessed[0].get() == 0)dovrebbe essere if (tPtrAccessed.get() == 0) ?
Rajeshwar,

@DougT. Credi che Java usi la stessa idea con i riferimenti? Morbido, duro, debole ecc?
Gansub,

20

Non trascurare boost::ptr_containerin alcun sondaggio di boost puntatori intelligenti. Possono essere preziosi in situazioni in cui un esempio std::vector<boost::shared_ptr<T> >sarebbe troppo lento.


In realtà, l'ultima volta che l'ho provato, il benchmarking ha mostrato che il gap prestazionale si era ridotto in modo significativo da quando l'ho scritto originariamente, almeno sul tipico PC HW! Tuttavia, l'approccio ptr_container più efficiente può presentare alcuni vantaggi in casi d'uso di nicchia.
giorno

12

Seguo il consiglio di guardare la documentazione. Non è così spaventoso come sembra. E alcuni brevi suggerimenti:

  • scoped_ptr- un puntatore viene automaticamente eliminato quando esce dal campo di applicazione. Nota: nessuna assegnazione possibile, ma non comporta costi generali
  • intrusive_ptr- puntatore conteggio dei riferimenti senza sovraccarico di smart_ptr. Tuttavia, l'oggetto stesso memorizza il conteggio dei riferimenti
  • weak_ptr- collabora shared_ptrcon le situazioni che comportano dipendenze circolari (leggi la documentazione e cerca su Google una bella immagine;)
  • shared_ptr - il generico, il più potente (e pesante) dei puntatori intelligenti (da quelli offerti da boost)
  • C'è anche un vecchio auto_ptr, che assicura che l'oggetto a cui punta viene distrutto automaticamente quando il controllo lascia un ambito. Tuttavia ha una semantica della copia diversa rispetto al resto dei ragazzi.
  • unique_ptr- verrà fornito con C ++ 0x

Risposta alla modifica:


8
Sono venuto qui perché ho trovato la documentazione di boost troppo spaventosa.
Francois Botha,
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.