Puntatori intelligenti (C++ moderno)

Nella programmazione C++ moderna, la libreria standard include puntatori intelligenti, che vengono usati per garantire che i programmi siano privi di memoria e perdite di risorse e siano sicuri dalle eccezioni.

Utilizzi per i puntatori intelligenti

I puntatori intelligenti vengono definiti nello std spazio dei nomi nel file di intestazione di <memoria> . Sono fondamentali per il linguaggio di programmazione di inizializzazione raii o acquisizione di risorse. L'obiettivo principale di questo linguaggio è assicurare che l'acquisizione delle risorse avvenga contemporaneamente all'inizializzazione dell'oggetto, in modo che tutte le risorse per l'oggetto vengano create e rese disponibili in una riga di codice. In pratica, il principio fondamentale di RAII è assegnare la proprietà delle risorse allocate dall'heap, ad esempio gli handle di oggetti di memoria o di memoria allocati in modo dinamico, a un oggetto allocato dallo stack il cui distruttore contiene il codice per eliminare o liberare la risorsa ed eventuale codice di pulizia associato.

Nella maggior parte dei casi, quando si inizializza un handle di risorsa o puntatore non elaborato per puntare a una risorsa effettiva, passare immediatamente il puntatore a un puntatore intelligente. Nel linguaggio C++ moderno, i puntatori non elaborati vengono utilizzati esclusivamente in blocchi di codice piccoli con ambito limitato, nei cicli o nelle funzioni di supporto per le quali le prestazioni sono importanti e non può crearsi confusione circa la proprietà.

Nell'esempio seguente viene fatto un confronto tra una dichiarazione di puntatore non elaborato e una dichiarazione di puntatore intelligente.

void UseRawPointer()
{
    // Using a raw pointer -- not recommended.
    Song* pSong = new Song(L"Nothing on You", L"Bruno Mars"); 

    // Use pSong...

    // Don't forget to delete!
    delete pSong;   
}

void UseSmartPointer()
{
    // Declare a smart pointer on stack and pass it the raw pointer.
    unique_ptr<Song> song2(new Song(L"Nothing on You", L"Bruno Mars"));

    // Use song2...
    wstring s = song2->duration_;
    //...

} // song2 is deleted automatically here.

Come illustrato nell'esempio, un puntatore intelligente è un modello di classe dichiarato nello stack e inizializzato tramite un puntatore non elaborato che punta a un oggetto allocato dall'heap. Dopo essere stato inizializzato, il puntatore intelligente detiene la proprietà del puntatore non elaborato. Ciò significa che il puntatore intelligente è responsabile dell'eliminazione della memoria specificata dal puntatore non elaborato. Il distruttore del puntatore intelligente contiene la chiamata per l'eliminazione e poiché il puntatore intelligente viene dichiarato nello stack, il relativo distruttore viene richiamato quando il puntatore intelligente esce dall'ambito, anche se viene generata un'eccezione in un punto precedente dello stack.

Accedere al puntatore incapsulato utilizzando gli operatori del puntatore noti, -> e *, di cui verrà eseguito l'overload per restituire il puntatore non elaborato incapsulato.

Il linguaggio del puntatore intelligente C++ ricorda la creazione di oggetti in linguaggi come C#: prima si crea l'oggetto e poi si permette al sistema di eliminarlo al momento opportuno. La differenza consiste nel fatto che nessun Garbage Collector separato viene eseguito in background. La memoria viene gestita tramite le regole di ambito C++ standard in modo che l'ambiente di runtime risulti più veloce e più efficiente.

Importante

Creare sempre puntatori intelligenti su una riga di codice separata e mai in un elenco di parametri, in modo da evitare anche le più piccole perdite di risorse dovute a alcune regole di allocazione dell'elenco di parametri.

Nell'esempio seguente viene illustrato come usare un unique_ptr tipo di puntatore intelligente dalla libreria standard C++ per incapsulare un puntatore a un oggetto di grandi dimensioni.


class LargeObject
{
public:
    void DoSomething(){}
};

void ProcessLargeObject(const LargeObject& lo){}
void SmartPointerDemo()
{    
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Pass a reference to a method.
    ProcessLargeObject(*pLarge);

} //pLarge is deleted automatically when function block goes out of scope.

Di seguito vengono illustrati i passaggi essenziali per utilizzare i puntatori intelligenti.

  1. Dichiarare il puntatore intelligente come variabile (locale) automatica. Non usare l'espressione new o malloc sul puntatore intelligente.

  2. Nel parametro di tipo specificare il tipo di riferimento del puntatore incapsulato.

  3. Passare un puntatore non elaborato a un newoggetto -ed nel costruttore del puntatore intelligente. (Alcune funzioni dell'utilità o costruttori del puntatore intelligente eseguono queste operazioni in modo automatico).

  4. Utilizzare gli operatori -> e * sottoposti a overload per accedere all'oggetto.

  5. Consentire al puntatore intelligente di eliminare l'oggetto.

I puntatori intelligenti sono progettati per essere estremamente efficaci sia in termini di memoria che di prestazioni. Ad esempio, l'unico membro dati in unique_ptr è il puntatore incapsulato. Ciò significa che unique_ptr è esattamente delle stesse dimensioni del puntatore, ovvero quattro o otto byte. L'accesso al puntatore incapsulato tramite l'overload del puntatore intelligente * e -> gli operatori non sono significativamente più lenti rispetto all'accesso diretto ai puntatori non elaborati.

I puntatori intelligenti hanno funzioni membro proprie, a cui si accede usando la notazione "punto". Ad esempio, alcuni puntatori intelligenti della libreria standard C++ hanno una funzione membro di reimpostazione che rilascia la proprietà del puntatore. Come illustrato nell'esempio seguente, questa caratteristica è utile quando è necessario liberare memoria di proprietà del puntatore intelligente prima che quest'ultimo esca dall'ambito.

void SmartPointerDemo2()
{
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Free the memory before we exit function block.
    pLarge.reset();

    // Do some other work...

}

I puntatori intelligenti consentono in genere di accedere direttamente al relativo puntatore non elaborato. I puntatori intelligenti della libreria standard C++ hanno una get funzione membro per questo scopo e CComPtr hanno un membro della classe pubblica p . Consentendo l'accesso diretto al puntatore sottostante, è possibile utilizzare il puntatore intelligente per gestire la memoria nel codice e passare il puntatore non elaborato al codice che non supporta i puntatori intelligenti.

void SmartPointerDemo4()
{
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Pass raw pointer to a legacy API
    LegacyLargeObjectFunction(pLarge.get());    
}

Tipi di puntatori intelligenti

Nella sezione seguente vengono riepilogati i diversi tipi di puntatori intelligenti disponibili nell'ambiente di programmazione Windows e ne viene descritto l'utilizzo.

Puntatori intelligenti della libreria standard C++

Utilizzare questi puntatori intelligenti come prima scelta per incapsulare i puntatori in oggetti C++ non aggiornati (POCO, plain old C++ object).

  • unique_ptr
    Consente esattamente un proprietario del puntatore sottostante. Utilizzarlo come scelta predefinita per POCO, a meno che non sia necessario un shared_ptr. Può essere spostato a un nuovo proprietario, ma non copiato o condiviso. Sostituisce auto_ptr, che è deprecato. Confrontare con boost::scoped_ptr. unique_ptr è piccolo ed efficiente; la dimensione è un puntatore e supporta riferimenti rvalue per l'inserimento e il recupero rapidi dalle raccolte di librerie standard C++. File di intestazione: <memory>. Per altre informazioni, vedere Procedura: Creare e usare istanze di unique_ptr e classe unique_ptr.

  • shared_ptr
    Puntatore intelligente con conteggio dei riferimenti. Utilizzarlo quando si desidera assegnare un puntatore non elaborato a più proprietari, ad esempio quando si restituisce una copia di un puntatore da un contenitore, ma si desidera conservare l'originale. Il puntatore non elaborato non viene eliminato finché tutti i proprietari di shared_ptr non sono usciti dall'ambito o non hanno ceduto in altro modo la proprietà. Ha le dimensioni di due puntatori, uno per l'oggetto e uno per il blocco di controllo condiviso che contiene il conteggio dei riferimenti. File di intestazione: <memory>. Per altre informazioni, vedere Procedura: Creare e usare istanze di shared_ptr e classe shared_ptr.

  • weak_ptr
    Puntatore intelligente per casi speciali da utilizzare insieme a shared_ptr. weak_ptr fornisce l'accesso a un oggetto di proprietà di una o più istanze di shared_ptr, ma non partecipa al conteggio dei riferimenti. Utilizzarlo quando si desidera osservare un oggetto, ma non è necessario che rimanga attivo. Necessario in alcuni casi per interrompere i riferimenti circolari tra istanze di shared_ptr. File di intestazione: <memory>. Per altre informazioni, vedere Procedura: Creare e usare istanze di weak_ptr e classe weak_ptr.

Puntatori intelligenti per gli oggetti COM (programmazione Classica di Windows)

Quando si utilizzano gli oggetti COM, eseguire il wrapping dei puntatori a interfaccia in un tipo di puntatore intelligente appropriato. La libreria ATL (Active Template Library) definisce diversi puntatori intelligenti che assolvono a funzioni diverse. È anche possibile utilizzare il tipo di puntatore intelligente _com_ptr_t, utilizzato dal compilatore durante la creazione di classi wrapper da file TLB. Si tratta della scelta migliore quando non si desidera includere i file di intestazione ATL.

Classe CComPtr
Utilizzarlo solo se non si può utilizzare ATL. Esegue il conteggio dei riferimenti mediante i metodi AddRef e Release. Per altre informazioni, vedere Procedura: Creare e usare istanze CComPtr e CComQIPtr.

Classe CComQIPtr
È simile a CComPtr, ma fornisce una sintassi semplificata per chiamare QueryInterface su oggetti COM. Per altre informazioni, vedere Procedura: Creare e usare istanze CComPtr e CComQIPtr.

Classe CComHeapPtr
Puntatore intelligente a oggetti che utilizzano CoTaskMemFree per liberare memoria.

Classe CComGITPtr
Puntatore intelligente per interfacce ottenute dalla tabella di interfaccia globale (GIT).

Classe _com_ptr_t
È simile a CComQIPtr in termini di funzionalità, ma non dipende dalle intestazioni ATL.

Puntatori intelligenti ATL per oggetti POCO

Oltre ai puntatori intelligenti per gli oggetti COM, ATL definisce anche puntatori intelligenti e raccolte di puntatori intelligenti per oggetti C++ precedenti (POCO). Nella programmazione classica di Windows, questi tipi sono utili alternative alle raccolte di librerie standard C++, soprattutto quando la portabilità del codice non è necessaria o quando non si desidera combinare i modelli di programmazione della libreria standard C++ e ATL.

Classe CAutoPtr
Puntatore intelligente che applica la proprietà univoca trasferendo la proprietà sulla copia. Paragonabile alla classe std::auto_ptr deprecata.

Classe CHeapPtr
Puntatore intelligente per gli oggetti allocati tramite la funzione C malloc .

Classe CAutoVectorPtr
Puntatore intelligente per le matrici allocate tramite new[].

Classe CAutoPtrArray
Classe che incapsula una matrice di elementi CAutoPtr.

Classe CAutoPtrList
Classe che incapsula metodi per modificare un elenco di nodi CAutoPtr.

Vedi anche

Puntatori
Riferimenti al linguaggio C++
Libreria standard C++