Che cos'è un puntatore intelligente e quando dovrei usarne uno?
Che cos'è un puntatore intelligente e quando dovrei usarne uno?
Risposte:
AGGIORNARE
Questa risposta è piuttosto vecchia e descrive quindi ciò che era "buono" al momento, che era un puntatore intelligente fornito dalla libreria Boost. Dal momento che C ++ 11, la libreria standard ha fornito sufficienti tipi puntatori intelligenti, e così si dovrebbe favorire l'uso di std::unique_ptr
, std::shared_ptr
e std::weak_ptr
.
C'era anche std::auto_ptr
. Era molto simile a un puntatore con ambito, tranne per il fatto che aveva anche la "speciale" pericolosa capacità di essere copiata, che trasferiva inaspettatamente la proprietà.
È stato deprecato in C ++ 11 e rimosso in C ++ 17 , quindi non dovresti usarlo.
std::auto_ptr<MyObject> p1 (new MyObject());
std::auto_ptr<MyObject> p2 = p1; // Copy and transfer ownership.
// p1 gets set to empty!
p2->DoSomething(); // Works.
p1->DoSomething(); // Oh oh. Hopefully raises some NULL pointer exception.
VECCHIA RISPOSTA
Un puntatore intelligente è una classe che avvolge un puntatore C ++ 'grezzo' (o 'nudo'), per gestire la durata dell'oggetto a cui si punta. Non esiste un singolo tipo di puntatore intelligente, ma tutti cercano di astrarre un puntatore non elaborato in modo pratico.
I puntatori intelligenti dovrebbero essere preferiti ai puntatori non elaborati. Se ritieni di dover usare i puntatori (prima considera se lo fai davvero ), normalmente vorresti usare un puntatore intelligente poiché questo può alleviare molti dei problemi con i puntatori non elaborati, dimenticandoti principalmente di eliminare l'oggetto e perdere memoria.
Con puntatori non elaborati, il programmatore deve distruggere esplicitamente l'oggetto quando non è più utile.
// Need to create the object to achieve some goal
MyObject* ptr = new MyObject();
ptr->DoSomething(); // Use the object in some way
delete ptr; // Destroy the object. Done with it.
// Wait, what if DoSomething() raises an exception...?
Un puntatore intelligente al confronto definisce una politica su quando l'oggetto viene distrutto. Devi ancora creare l'oggetto, ma non devi più preoccuparti di distruggerlo.
SomeSmartPtr<MyObject> ptr(new MyObject());
ptr->DoSomething(); // Use the object in some way.
// Destruction of the object happens, depending
// on the policy the smart pointer class uses.
// Destruction would happen even if DoSomething()
// raises an exception
La politica più semplice in uso riguarda l'ambito dell'oggetto wrapper puntatore intelligente, come implementato da boost::scoped_ptr
o std::unique_ptr
.
void f()
{
{
std::unique_ptr<MyObject> ptr(new MyObject());
ptr->DoSomethingUseful();
} // ptr goes out of scope --
// the MyObject is automatically destroyed.
// ptr->Oops(); // Compile error: "ptr" not defined
// since it is no longer in scope.
}
Si noti che le std::unique_ptr
istanze non possono essere copiate. Ciò impedisce che il puntatore venga eliminato più volte (in modo errato). Tuttavia, è possibile passare riferimenti ad esso ad altre funzioni chiamate.
std::unique_ptr
sono utili quando si desidera associare la durata dell'oggetto a un determinato blocco di codice o se lo si è incorporato come dati membro all'interno di un altro oggetto, la durata di quell'altro oggetto. L'oggetto esiste fino all'uscita dal blocco di codice contenente o fino a quando l'oggetto contenitore non viene distrutto.
Una politica di puntatore intelligente più complessa prevede il conteggio dei riferimenti del puntatore. Ciò consente di copiare il puntatore. Quando l'ultimo "riferimento" all'oggetto viene distrutto, l'oggetto viene eliminato. Questa politica è implementata da boost::shared_ptr
e std::shared_ptr
.
void f()
{
typedef std::shared_ptr<MyObject> MyObjectPtr; // nice short alias
MyObjectPtr p1; // Empty
{
MyObjectPtr p2(new MyObject());
// There is now one "reference" to the created object
p1 = p2; // Copy the pointer.
// There are now two references to the object.
} // p2 is destroyed, leaving one reference to the object.
} // p1 is destroyed, leaving a reference count of zero.
// The object is deleted.
I puntatori contati di riferimento sono molto utili quando la durata del tuo oggetto è molto più complicata e non è legata direttamente a una particolare sezione di codice o ad un altro oggetto.
C'è un inconveniente per fare riferimento ai puntatori contati: la possibilità di creare un riferimento pendente:
// Create the smart pointer on the heap
MyObjectPtr* pp = new MyObjectPtr(new MyObject())
// Hmm, we forgot to destroy the smart pointer,
// because of that, the object is never destroyed!
Un'altra possibilità è la creazione di riferimenti circolari:
struct Owner {
std::shared_ptr<Owner> other;
};
std::shared_ptr<Owner> p1 (new Owner());
std::shared_ptr<Owner> p2 (new Owner());
p1->other = p2; // p1 references p2
p2->other = p1; // p2 references p1
// Oops, the reference count of of p1 and p2 never goes to zero!
// The objects are never destroyed!
Per aggirare questo problema, sia Boost che C ++ 11 hanno definito a weak_ptr
per definire un riferimento debole (non contato) a a shared_ptr
.
std::auto_ptr<MyObject> p1 (new MyObject());
invece std::auto_ptr<MyObject> p1 (new Owner());
?
const std::auto_ptr
è sicuro da usare, se sei bloccato con C ++ 03. L'ho usato abbastanza per il modello pimpl fino a quando non ho avuto accesso a C ++ 11.
Ecco una semplice risposta per questi giorni del moderno C ++ (C ++ 11 e versioni successive):
std::unique_ptr
quando non si intende contenere più riferimenti allo stesso oggetto. Ad esempio, utilizzarlo per un puntatore alla memoria che viene allocato all'ingresso di un ambito e disallocato all'uscita dall'ambito.std::shared_ptr
quando si desidera fare riferimento al proprio oggetto da più punti - e non si desidera che il proprio oggetto venga disallocato fino a quando tutti questi riferimenti non saranno scomparsi.std::weak_ptr
quando si desidera fare riferimento al proprio oggetto da più punti, per quei riferimenti per i quali è ok ignorare e deallocare (quindi noteranno solo che l'oggetto è sparito quando si tenta di dereferenziare).boost::
puntatori intelligenti o std::auto_ptr
tranne in casi speciali sui quali è possibile leggere se è necessario.T*
è quello std::unique_ptr<T>
che std::weak_ptr<T>
deve farestd::shared_ptr<T>
Il puntatore intelligente è un tipo di puntatore con alcune funzionalità aggiuntive, ad esempio deallocazione automatica della memoria, conteggio dei riferimenti, ecc.
Piccola introduzione è disponibile a pagina Puntatori intelligenti: cosa, perché, quale? .
Uno del tipo di puntatore intelligente semplice è std::auto_ptr
(capitolo 20.4.5 dello standard C ++), che consente di deallocare automaticamente la memoria quando non rientra nell'ambito e che è più robusto del semplice utilizzo del puntatore quando vengono generate eccezioni, sebbene meno flessibile.
Un altro tipo conveniente è quello boost::shared_ptr
che implementa il conteggio dei riferimenti e disloca automaticamente la memoria quando non rimane alcun riferimento all'oggetto. Questo aiuta a evitare perdite di memoria ed è facile da usare per implementare RAII .
L'argomento è approfondito nel libro "Modelli C ++: la guida completa" di David Vandevoorde, Nicolai M. Josuttis , capitolo 20. Puntatori intelligenti. Alcuni argomenti trattati:
std::auto_ptr
è deprecato e altamente scoraggiante in quanto è possibile trasferire accidentalmente la proprietà. - C ++ 11 elimina la necessità di Boost, uso: std::unique_ptr
, std::shared_ptr
estd::weak_ptr
Le definizioni fornite da Chris, Sergdev e Llyod sono corrette. Preferisco una definizione più semplice, però, solo per semplificarmi la vita: un puntatore intelligente è semplicemente una classe che sovraccarica gli operatori ->
e *
. Il che significa che il tuo oggetto sembra semanticamente un puntatore ma puoi farlo fare cose più interessanti, tra cui il conteggio dei riferimenti, la distruzione automatica ecc.
shared_ptr
E auto_ptr
sono sufficienti nella maggior parte dei casi, ma arrivano con il loro set di piccole idiosincrasie.
Un puntatore intelligente è come un normale puntatore (tipizzato), come "char *", tranne quando il puntatore stesso esce dal campo di applicazione e quindi viene eliminato anche ciò a cui punta. Puoi usarlo come faresti con un normale puntatore, usando "->", ma non se hai bisogno di un vero puntatore ai dati. Per questo, puoi usare "& * ptr".
È utile per:
Oggetti che devono essere allocati con nuovi, ma che vorresti avere la stessa durata di qualcosa nello stack. Se l'oggetto viene assegnato a un puntatore intelligente, verranno eliminati quando il programma esce da quella funzione / blocco.
Membri dei dati delle classi, in modo che quando l'oggetto viene eliminato anche tutti i dati di proprietà vengano eliminati, senza alcun codice speciale nel distruttore (sarà necessario assicurarsi che il distruttore sia virtuale, il che è quasi sempre una buona cosa da fare) .
Potresti non voler utilizzare un puntatore intelligente quando:
Guarda anche:
La maggior parte dei tipi di puntatori intelligenti gestiscono l'eliminazione dell'oggetto puntatore a voi. È molto utile perché non devi più pensare a smaltire gli oggetti manualmente.
I puntatori intelligenti più comunemente usati sono std::tr1::shared_ptr
(o boost::shared_ptr
), e, meno comunemente, std::auto_ptr
. Raccomando l'uso regolare di shared_ptr
.
shared_ptr
è molto versatile e si occupa di una grande varietà di scenari di smaltimento, inclusi i casi in cui gli oggetti devono essere "passati attraverso i confini della DLL" (il caso dell'incubo comune se libc
si usano s diversi tra il codice e le DLL).
Un puntatore intelligente è un oggetto che agisce come un puntatore, ma fornisce inoltre controllo su costruzione, distruzione, copia, spostamento e dereferenziazione.
È possibile implementare il proprio puntatore intelligente, ma molte librerie forniscono anche implementazioni di puntatori intelligenti, ognuna con vantaggi e svantaggi diversi.
Ad esempio, Boost fornisce le seguenti implementazioni di puntatori intelligenti:
shared_ptr<T>
è un puntatore T
all'utilizzo di un conteggio di riferimento per determinare quando l'oggetto non è più necessario.scoped_ptr<T>
è un puntatore eliminato automaticamente quando esce dall'ambito. Nessun incarico è possibile.intrusive_ptr<T>
è un altro puntatore di conteggio dei riferimenti. Offre prestazioni migliori rispetto a shared_ptr
, ma richiede al tipo T
di fornire il proprio meccanismo di conteggio dei riferimenti.weak_ptr<T>
è un puntatore debole, che lavora insieme shared_ptr
per evitare riferimenti circolari.shared_array<T>
è come shared_ptr
, ma per array di T
.scoped_array<T>
è come scoped_ptr
, ma per array di T
.Queste sono solo una descrizione lineare di ciascuna e possono essere utilizzate secondo necessità, per ulteriori dettagli ed esempi si può consultare la documentazione di Boost.
Inoltre, la libreria standard C ++ fornisce tre puntatori intelligenti; std::unique_ptr
per proprietà unica, std::shared_ptr
per proprietà condivisa e std::weak_ptr
. std::auto_ptr
esisteva in C ++ 03 ma ora è deprecato.
scoped_ptr
non è come un dichiarato localmente, const unique_ptr
che viene eliminato anche all'uscita dall'ambito.
Ecco il link per risposte simili: http://sickprogrammersarea.blogspot.in/2014/03/technical-interview-questions-on-c_6.html
Un puntatore intelligente è un oggetto che agisce, appare e si sente come un normale puntatore ma offre più funzionalità. In C ++, i puntatori intelligenti sono implementati come classi di modelli che incapsulano un puntatore e sovrascrivono gli operatori di puntatori standard. Hanno una serie di vantaggi rispetto ai puntatori regolari. Sono garantiti per essere inizializzati come puntatori null o puntatori a un oggetto heap. L'indirizzamento tramite un puntatore null è verificato. Nessuna cancellazione è mai necessaria. Gli oggetti vengono liberati automaticamente quando l'ultimo puntatore è scomparso. Un problema significativo con questi puntatori intelligenti è che, a differenza dei puntatori normali, non rispettano l'eredità. I puntatori intelligenti non sono attraenti per il codice polimorfico. Di seguito è riportato un esempio per l'implementazione di puntatori intelligenti.
Esempio:
template <class X>
class smart_pointer
{
public:
smart_pointer(); // makes a null pointer
smart_pointer(const X& x) // makes pointer to copy of x
X& operator *( );
const X& operator*( ) const;
X* operator->() const;
smart_pointer(const smart_pointer <X> &);
const smart_pointer <X> & operator =(const smart_pointer<X>&);
~smart_pointer();
private:
//...
};
Questa classe implementa un puntatore intelligente su un oggetto di tipo X. L'oggetto stesso si trova sull'heap. Ecco come usarlo:
smart_pointer <employee> p= employee("Harris",1333);
Come altri operatori sovraccarichi, p si comporterà come un normale puntatore,
cout<<*p;
p->raise_salary(0.5);
http://en.wikipedia.org/wiki/Smart_pointer
In informatica, un puntatore intelligente è un tipo di dati astratto che simula un puntatore fornendo allo stesso tempo funzionalità aggiuntive, come la garbage collection automatica o il controllo dei limiti. Queste funzionalità aggiuntive hanno lo scopo di ridurre i bug causati dall'uso improprio dei puntatori mantenendo l'efficienza. I puntatori intelligenti in genere tengono traccia degli oggetti che puntano ad essi ai fini della gestione della memoria. L'uso improprio dei puntatori è una delle principali fonti di bug: l'allocazione, la deallocazione e il riferimento costanti che devono essere eseguiti da un programma scritto utilizzando i puntatori rendono molto probabile che si verifichino perdite di memoria. I puntatori intelligenti cercano di prevenire perdite di memoria rendendo automatica la deallocazione delle risorse: quando il puntatore a un oggetto (o l'ultimo di una serie di puntatori) viene distrutto,
Sia T una classe in questo tutorial I puntatori in C ++ possono essere divisi in 3 tipi:
1) Puntatori non elaborati :
T a;
T * _ptr = &a;
Conservano un indirizzo di memoria in una posizione nella memoria. Usare con cautela, poiché i programmi diventano difficili da tenere traccia.
Puntatori con dati const o indirizzo {Leggi indietro}
T a ;
const T * ptr1 = &a ;
T const * ptr1 = &a ;
Puntatore a un tipo di dati T che è una const. Ciò significa che non è possibile modificare il tipo di dati utilizzando il puntatore. cioè *ptr1 = 19
; non funzionerà. Ma puoi spostare il puntatore. cioè ptr1++ , ptr1--
; ecc funzionerà. Leggi all'indietro: puntatore al tipo T che è const
T * const ptr2 ;
Un puntatore const a un tipo di dati T. Ciò significa che non è possibile spostare il puntatore ma è possibile modificare il valore indicato dal puntatore. cioè *ptr2 = 19
funzionerà ma ptr2++ ; ptr2--
ecc non funzionerà. Leggi indietro: puntatore const a un tipo T
const T * const ptr3 ;
Un puntatore const a un tipo di dati const T. Ciò significa che non è possibile spostare il puntatore né modificare il puntatore del tipo di dati in modo che diventi il puntatore. cioè ptr3-- ; ptr3++ ; *ptr3 = 19;
non funzionerà
3) Puntatori intelligenti : { #include <memory>
}
Puntatore condiviso :
T a ;
//shared_ptr<T> shptr(new T) ; not recommended but works
shared_ptr<T> shptr = make_shared<T>(); // faster + exception safe
std::cout << shptr.use_count() ; // 1 // gives the number of "
things " pointing to it.
T * temp = shptr.get(); // gives a pointer to object
// shared_pointer used like a regular pointer to call member functions
shptr->memFn();
(*shptr).memFn();
//
shptr.reset() ; // frees the object pointed to be the ptr
shptr = nullptr ; // frees the object
shptr = make_shared<T>() ; // frees the original object and points to new object
Implementato utilizzando il conteggio dei riferimenti per tenere traccia di quante "cose" puntano all'oggetto puntato dal puntatore. Quando questo conteggio va a 0, l'oggetto viene automaticamente eliminato, ovvero l'oggetto viene eliminato quando tutto lo share_ptr che punta all'oggetto non rientra nell'ambito. Questo elimina il mal di testa di dover eliminare gli oggetti che hai allocato usando new.
Puntatore debole: aiuta a gestire il riferimento ciclico che si presenta quando si utilizza il puntatore condiviso Se si hanno due oggetti puntati da due puntatori condivisi e c'è un puntatore condiviso interno che punta a vicenda puntatore condiviso, ci sarà un riferimento ciclico e l'oggetto non essere eliminato quando i puntatori condivisi non rientrano nell'ambito. Per risolvere questo, cambia il membro interno da shared_ptr a weak_ptr. Nota: per accedere all'elemento puntato da un puntatore debole usare lock (), questo restituisce un punto debole.
T a ;
shared_ptr<T> shr = make_shared<T>() ;
weak_ptr<T> wk = shr ; // initialize a weak_ptr from a shared_ptr
wk.lock()->memFn() ; // use lock to get a shared_ptr
// ^^^ Can lead to exception if the shared ptr has gone out of scope
if(!wk.expired()) wk.lock()->memFn() ;
// Check if shared ptr has gone out of scope before access
Vedi: quando è utile std :: weak_ptr?
Puntatore unico: puntatore intelligente leggero con proprietà esclusiva. Utilizzare quando il puntatore punta a oggetti unici senza condividere gli oggetti tra i puntatori.
unique_ptr<T> uptr(new T);
uptr->memFn();
//T * ptr = uptr.release(); // uptr becomes null and object is pointed to by ptr
uptr.reset() ; // deletes the object pointed to by uptr
Per modificare l'oggetto a cui punta il ptr univoco, utilizzare la semantica di spostamento
unique_ptr<T> uptr1(new T);
unique_ptr<T> uptr2(new T);
uptr2 = std::move(uptr1);
// object pointed by uptr2 is deleted and
// object pointed by uptr1 is pointed to by uptr2
// uptr1 becomes null
Riferimenti: possono essenzialmente essere considerati puntatori const, ovvero un puntatore che è const e non può essere spostato con una migliore sintassi.
Vedi: Quali sono le differenze tra una variabile puntatore e una variabile di riferimento in C ++?
r-value reference : reference to a temporary object
l-value reference : reference to an object whose address can be obtained
const reference : reference to a data type which is const and cannot be modified
Riferimento: https://www.youtube.com/channel/UCEOGtxYTB6vo6MQ-WQ9W_nQ Grazie ad Andre per aver sottolineato questa domanda.
Un puntatore intelligente è una classe, un wrapper di un puntatore normale. A differenza dei normali puntatori, il cerchio di vita di smart point si basa su un conteggio di riferimento (quante volte viene assegnato l'oggetto puntatore intelligente). Pertanto, ogni volta che un puntatore intelligente viene assegnato a un altro, il riferimento interno conta più più. E ogni volta che l'oggetto esce dall'ambito, il conteggio di riferimento meno meno.
Il puntatore automatico, sebbene sembri simile, è totalmente diverso dal puntatore intelligente. È una classe conveniente che dealloca la risorsa ogni volta che un oggetto puntatore automatico esce dall'ambito variabile. In una certa misura, fa funzionare un puntatore (alla memoria allocata dinamicamente) in modo simile a una variabile di stack (allocata staticamente nel tempo di compilazione).
I puntatori intelligenti sono quelli in cui non devi preoccuparti della disallocazione della memoria, della condivisione delle risorse e del trasferimento.
Puoi benissimo usare questi puntatori nello stesso modo in cui qualsiasi allocazione funziona in Java. In java Garbage Collector fa il trucco, mentre in Smart Pointers il trucco è fatto dai Distruttori.
Le risposte esistenti sono buone ma non coprono cosa fare quando un puntatore intelligente non è la risposta (completa) al problema che si sta tentando di risolvere.
Tra l'altro (spiegato bene in altre risposte) l'uso di un puntatore intelligente è una possibile soluzione a Come possiamo usare una classe astratta come tipo di ritorno di funzione? che è stato contrassegnato come duplicato di questa domanda. Tuttavia, la prima domanda da porsi se si è tentati di specificare una classe base astratta (o di fatto una qualsiasi) come tipo di ritorno in C ++ è "cosa intendi veramente?". C'è una buona discussione (con ulteriori riferimenti) sulla programmazione orientata agli oggetti idiomatica in C ++ (e come questo è diverso da altri linguaggi) nella documentazione della libreria contenitore puntatore boost. In sintesi, in C ++ devi pensare alla proprietà. Quali smart pointer ti aiutano, ma non sono l'unica soluzione, o sempre una soluzione completa (non ti danno una copia polimorfica) e non sono sempre una soluzione che vuoi esporre nella tua interfaccia (e una funzione di ritorno suona terribile molto simile a un'interfaccia). Potrebbe essere sufficiente restituire un riferimento, ad esempio. Ma in tutti questi casi (puntatore intelligente, contenitore puntatore o semplicemente restituendo un riferimento) è stato modificato il ritorno da un valore a una forma di riferimento . Se hai davvero bisogno di una copia, potresti aver bisogno di aggiungere più "idioma" o di andare oltre OO idiomatico (o altrimenti) in C ++ a polimorfismo più generico usando librerie come Adobe Poly o Boost..