C ++ in modo succinto l'acquisizione delle risorse è l'inizializzazione

Cos'è RAII?

RAII sta per "acquisizione delle risorse è inizializzazione". RAII è un modello di progettazione che utilizza codice C ++ per eliminare le perdite di risorse. Le perdite di risorse si verificano quando una risorsa acquisita dal programma non viene successivamente rilasciata. L'esempio più familiare è una perdita di memoria. Dato che C ++ non ha un GC come fa C #, devi fare attenzione a garantire che la memoria allocata dinamicamente venga liberata. Altrimenti, perderai quella memoria. Le perdite di risorse possono anche comportare l'impossibilità di aprire un file perché il file system pensa che sia già aperto, l'impossibilità di ottenere un blocco in un programma multi-thread o l'impossibilità di rilasciare un oggetto COM.


Come funziona RAII?

RAII funziona grazie a tre fatti di base.

  1. Quando un oggetto di durata di archiviazione automatica esce dall'ambito, viene eseguito il suo distruttore.
  2. Quando si verifica un'eccezione, tutti gli oggetti di durata automatica che sono stati completamente creati dall'ultimo blocco try iniziato vengono distrutti nell'ordine inverso rispetto a quello in cui sono stati creati prima che venga richiamato qualsiasi gestore di catch.
  3. Se nidifichi try-blocks e nessuno dei catch catcher di un try-block interno gestisce quel tipo di eccezione, allora l'eccezione si propaga al try-block esterno. Tutti gli oggetti di durata automatica che sono stati interamente costruiti all'interno di quel try-block esterno vengono quindi distrutti in ordine di creazione inversa prima che venga richiamato qualsiasi gestore catch e così via, fino a quando qualcosa non recupera l'eccezione o il programma si arresta in modo anomalo.

RAII aiuta a garantire il rilascio delle risorse, senza che si verifichino eccezioni, semplicemente utilizzando gli oggetti di durata di archiviazione automatica che contengono le risorse. È simile alla combinazione di System.IDisposable interfaccia con l'istruzione using in C #. Una volta che l'esecuzione lascia il blocco corrente, sia attraverso l'esecuzione riuscita o un'eccezione, le risorse vengono liberate.

Quando si tratta di eccezioni, una parte fondamentale da ricordare è che solo gli oggetti completamente costruiti vengono distrutti. Se si riceve un'eccezione nel mezzo di una funzione di costruzione e l'ultimo blocco try è iniziato all'esterno del costruttore, poiché l'oggetto non è completamente costruito, il suo distruttore non verrà eseguito.

Questo non significa che le sue variabili membro, che sono oggetti, non saranno distrutte. Qualsiasi oggetto variabile membro che è stato completamente costruito all'interno del costruttore prima che si verificasse l'eccezione è costituito da oggetti di durata automatica completamente costruiti. Pertanto, quegli oggetti membri verranno distrutti come qualsiasi altro oggetto completamente costruito.

Questo è il motivo per cui dovresti sempre inserire allocazioni dinamiche all'interno di entrambi std :: unique_ptr o std :: shared_ptr. Le istanze di questi tipi diventano oggetti completamente costruiti quando l'allocazione ha successo. Anche se il costruttore per l'oggetto che si sta creando ha esito negativo, il std :: unique_ptr le risorse saranno liberate dal suo distruttore e dal std :: shared_ptr le risorse avranno il loro conteggio di riferimento decrementato e saranno liberate se il conteggio diventa zero.

RAII non riguarda solo shared_ptr e unique_ptr, naturalmente. Si applica anche ad altri tipi di risorse, come un oggetto file, in cui l'acquisizione è l'apertura del file e il distruttore assicura che il file sia chiuso correttamente. Questo è un esempio particolarmente valido poiché è sufficiente creare quel codice solo una volta, quando si scrive la classe, piuttosto che ancora e ancora, che è ciò che è necessario fare se si scrive la logica di chiusura in ogni punto in cui si deve aprire un file.


Come si usa RAII?

L'uso di RAII è descritto dal suo nome: L'acquisizione di una risorsa dinamica dovrebbe completare l'inizializzazione di un oggetto. Se segui questo modello a risorsa singola per oggetto, è impossibile finire con una perdita di risorse. O acquisirai correttamente la risorsa, nel qual caso l'oggetto che lo incapsula terminerà la costruzione e sarà soggetto a distruzione, o il tentativo di acquisizione fallirà, nel qual caso non hai acquisito la risorsa; quindi, non ci sono risorse da rilasciare.

Il distruttore di un oggetto che incapsula una risorsa deve rilasciare quella risorsa. Questo, tra le altre cose, è una delle ragioni importanti per cui i distruttori non dovrebbero mai lanciare eccezioni, eccetto quelle che catturano e gestiscono dentro di loro.

Se il distruttore ha lanciato un'eccezione non rilevata, quindi, per citare Bjarne Stroustrup, "Tutti i tipi di cose brutte possono accadere perché le regole di base della libreria standard e il linguaggio stesso verranno violati. Non farlo. "

Come ha detto, non farlo. Assicurati di sapere quali eccezioni, se del caso, tutto ciò che chiami nei tuoi distruttori potrebbero generare, così puoi assicurarti di gestirle correttamente.

Ora potresti pensare che se segui questo schema, finirai per scrivere una tonnellata di classi. Occasionalmente scriverò una classe extra qua e là, ma non è probabile che ne scriviate troppe a causa di puntatori intelligenti. I puntatori intelligenti sono anche oggetti. La maggior parte dei tipi di risorse dinamiche può essere inserita in almeno una delle classi di puntatori intelligenti esistenti. Quando si inserisce un'acquisizione di risorse all'interno di un puntatore intelligente appropriato, se l'acquisizione ha esito positivo, l'oggetto del puntatore intelligente verrà completamente creato. Se si verifica un'eccezione, verrà chiamato il distruttore dell'oggetto smart pointer e la risorsa verrà liberata.

Esistono diversi tipi di puntatori intelligenti importanti. Diamo un'occhiata a loro.

Il std :: unique_ptr Funzione

Il puntatore unico, std :: unique_ptr, è progettato per contenere un puntatore a un oggetto assegnato dinamicamente. Dovresti usare questo tipo solo quando vuoi che un puntatore all'oggetto esista. È una classe template che accetta un argomento template obbligatorio e facoltativo. L'argomento obbligatorio è il tipo di puntatore che manterrà. Per esempio auto result = std :: unique_ptr(nuovo int ()); creerà un puntatore univoco che contiene un * int. L'argomento facoltativo è il tipo di deleter. Vediamo come scrivere un deleter in un campione in arrivo. In genere, puoi evitare di specificare un deleter poiché il default_deleter, che viene fornito per te se non viene specificato alcun deleter, copre quasi tutti i casi che puoi immaginare.

Una classe che ha std :: unique_ptr come variabile membro non può avere un costruttore di copie predefinito. Copia semantica sono disabilitati per std :: unique_ptr. Se si desidera un costruttore di copie in una classe con un puntatore univoco, è necessario scriverlo. Dovresti anche scrivere un sovraccarico per l'operatore di copia. Normalmente, vuoi std :: shared_ptr in quel caso.

Tuttavia, potresti avere qualcosa come una serie di dati. Potresti anche volere qualsiasi copia della classe per creare una copia dei dati così come esiste in quel momento. In tal caso, un puntatore univoco con un costruttore di copia personalizzato potrebbe essere la scelta giusta.

std :: unique_ptr è definito nel file di intestazione.

std :: unique_ptr ha quattro funzioni membro di interesse.

La funzione get member restituisce il puntatore memorizzato. Se è necessario chiamare una funzione a cui è necessario passare il puntatore contenuto, utilizzare get per recuperare una copia del puntatore.

La funzione membro di rilascio restituisce anche il puntatore memorizzato, ma rilascia invalida il unique_ptr nel processo sostituendo il puntatore memorizzato con un puntatore nullo. Se si dispone di una funzione in cui si desidera creare un oggetto dinamico e quindi restituirlo, mantenendo comunque la sicurezza delle eccezioni, utilizzare std: unique_ptr per memorizzare l'oggetto creato dinamicamente e quindi restituire il risultato del rilascio di chiamata. Questo ti dà un'eccezione di sicurezza mentre ti permette di restituire l'oggetto dinamico senza distruggerlo con il std :: unique_ptrè il distruttore quando il controllo esce dalla funzione al ritorno del valore del puntatore rilasciato alla fine.

La funzione membro di scambio consente a due puntatori unici di scambiare i loro puntatori memorizzati, quindi se A sta tenendo un puntatore su X e B sta tenendo un puntatore su Y, il risultato della chiamata A :: swap (B); è che A manterrà un puntatore a Y, e B terrà un puntatore a X. Anche i deletatori saranno scambiati, quindi se hai un deleter personalizzato per uno o entrambi i puntatori univoci, assicurati che ognuno mantenere il suo deleter associato.

La funzione membro di reset fa sì che l'oggetto a cui punta il puntatore memorizzato, se presente, venga distrutto nella maggior parte dei casi. Se il puntatore memorizzato corrente è nullo, nulla viene distrutto. Se passi un puntatore all'oggetto a cui punta il puntatore memorizzato corrente, nulla viene distrutto. È possibile scegliere di passare un nuovo puntatore, nullptr o chiamare la funzione senza parametri. Se passi un nuovo puntatore, quel nuovo oggetto viene memorizzato. Se passi in nullptr, il puntatore univoco memorizzerà null. Chiamare la funzione senza parametri equivale a chiamarla con nullptr.

Il std :: shared_ptr Funzione

Il puntatore condiviso, std :: shared_ptr, è progettato per contenere un puntatore a un oggetto assegnato dinamicamente e per mantenere un conteggio di riferimento per esso. Non è magia; se crei due puntatori condivisi e li passi con un puntatore allo stesso oggetto, ti ritroverai con due puntatori condivisi, ognuno con un conteggio di riferimento di 1, non di 2. Il primo che viene distrutto rilascerà la risorsa sottostante, dando risultati catastrofici quando si tenta di utilizzare l'altro o quando l'altro viene distrutto e tenta di rilasciare la risorsa sottostante già rilasciata.

Per utilizzare correttamente il puntatore condiviso, creare un'istanza con un puntatore all'oggetto e quindi creare tutti gli altri puntatori condivisi per quell'oggetto da un puntatore condiviso valido esistente per quell'oggetto. Questo garantisce un conteggio di riferimento comune, quindi la risorsa avrà una durata adeguata. Diamo un'occhiata ad un rapido esempio per vedere i modi giusti e sbagliati per creare oggetti shared_ptr.

Esempio: SharedPtrSample \ SharedPtrSample.cpp

#includere  #includere  #includere  #include "... /pchar.h" usando namespace std; struct TwoInts TwoInts (void): A (), B ()  TwoInts (int a, int b): A (a), B (b)  int A; int B; ; wostream e operatore<<(wostream& stream, TwoInts* v)  stream << v->UN << L" " << v->B; flusso di ritorno;  int _pmain (int / * argc * /, _pchar * / * argv * / []) //// Bad: risultati in double free. // prova // // TwoInts * p_i = new TwoInts (10, 20); // auto sp1 = shared_ptr(pi); // auto sp2 = shared_ptr(pi); // p_i = nullptr; // wcout << L"sp1 count is " << sp1.use_count() << L"." << endl << // L"sp2 count is " << sp2.use_count() << L"." << endl; // //catch(exception& e) // // wcout << L"There was an exception." << endl; // wcout << e.what() << endl << endl; // //catch(… ) // // wcout << L"There was an exception due to a double free " << // L"because we tried freeing p_i twice!" << endl; // // This is one right way to create shared_ptrs.  auto sp1 = shared_ptr(nuovo TwoInts (10, 20)); auto sp2 = shared_ptr(SP1); wcout << L"sp1 count is " << sp1.use_count() << L"." << endl << L"sp2 count is " << sp2.use_count() << L"." << endl; wcout << L"sp1 value is " << sp1 << L"." << endl << L"sp2 value is " << sp2 << L"." << endl;  // This is another right way. The std::make_shared function takes the // type as its template argument, and then the argument value(s) to the // constructor you want as its parameters, and it automatically // constructs the object for you. This is usually more memory- // efficient, as the reference count can be stored with the // shared_ptr's pointed-to object at the time of the object's creation.  auto sp1 = make_shared(10, 20); auto sp2 = shared_ptr(SP1); wcout << L"sp1 count is " << sp1.use_count() << L"." << endl << L"sp2 count is " << sp2.use_count() << L"." << endl; wcout << L"sp1 value is " << sp1 << L"." << endl << L"sp2 value is " << sp2 << L"." << endl;  return 0; 

std :: shared_ptr è definito nel file di intestazione.

std :: shared_ptr ha cinque funzioni membro di interesse.

La funzione get member funziona allo stesso modo della funzione std :: unique_ptr :: get member.

La funzione membro use_count restituisce un valore lungo, che indica quale sia il numero di riferimento corrente per l'oggetto di destinazione. Questo non include riferimenti deboli.

La funzione membro univoca restituisce un valore bool che informa se questo particolare puntatore condiviso è l'unico proprietario dell'oggetto target.

La funzione membro di scambio funziona allo stesso modo di std :: :: unique_ptr di swap funzione membro, con l'aggiunta che il riferimento conta per le risorse rimangono le stesse.

La funzione membro di ripristino riduce il conteggio dei riferimenti per la risorsa sottostante e la distrugge se il conteggio delle risorse diventa zero. Se viene passato un puntatore a un oggetto, il puntatore condiviso lo memorizzerà e inizierà un nuovo conteggio di riferimenti per quel puntatore. Se viene passato nullptr o se non viene passato alcun parametro, il puntatore condiviso memorizzerà null.

Il std :: make_shared Funzione

Il std :: make_shared la funzione template è un modo conveniente per costruire un'iniziale std :: shared_ptr. Come abbiamo visto in precedenza SharedPtrSample, si passa il tipo come argomento del template e poi si passa semplicemente gli argomenti, se ce ne sono, per il costruttore desiderato. std :: make_shared costruirà un'istanza heap del tipo di oggetto argomento template e lo trasformerà in a std :: shared_ptr. Puoi quindi passare quello std :: shared_ptr come argomento per il std :: shared_ptr costruttore per creare più riferimenti a quell'oggetto condiviso.


ComPtr in WRL per le app Metro-Style

La libreria dei modelli di runtime di Windows (WRL) fornisce un puntatore intelligente denominato ComPtr all'interno dello spazio dei nomi Microsoft :: WRL per l'utilizzo con gli oggetti COM nelle applicazioni in stile Metro di Windows 8. Il puntatore si trova nel intestazione, come parte di Windows SDK (versione minima 8.0).

La maggior parte delle funzionalità del sistema operativo che è possibile utilizzare nelle applicazioni in stile Metro è esposta da Windows Runtime ("WinRT"). Gli oggetti WinRT forniscono le proprie funzionalità di conteggio dei riferimenti automatici per la creazione e la distruzione degli oggetti. Alcune funzionalità di sistema, come Direct3D, richiedono l'uso e la manipolazione diretta tramite la classica COM. ComPtr gestisce per te il conteggio dei riferimenti basato su IUnknown di COM. Fornisce inoltre wrapper convenienti per QueryInterface e include altre funzionalità utili per i puntatori intelligenti.

Le funzioni dei due membri che in genere si utilizzano sono Come ottenere un'interfaccia diversa per l'oggetto COM sottostante e Ottenere di prendere un puntatore all'interfaccia dell'oggetto COM sottostante che contiene il ComPtr (questo è l'equivalente di std :: unique_ptr :: get).

A volte utilizzerai Detach, che funziona allo stesso modo di std :: unique_ptr :: release ma ha un nome diverso perché la release in COM implica il decremento del conteggio dei riferimenti e Detach non lo fa.

È possibile utilizzare ReleaseAndGetAddressOf per le situazioni in cui si dispone di un ComPtr esistente che potrebbe già contenere un oggetto COM e si desidera sostituirlo con un nuovo oggetto COM dello stesso tipo. ReleaseAndGetAddressOf fa la stessa cosa della funzione membro GetAddressOf, ma prima rilascia l'interfaccia sottostante, se presente.


Eccezioni in C++

A differenza di .NET, dove tutte le eccezioni derivano da System.Exception e hanno metodi e proprietà garantite, le eccezioni C ++ non sono richieste per derivare da qualcosa; né sono nemmeno richiesti di essere tipi di classe. In C ++, lancia L "Hello World!"; è perfettamente accettabile per il compilatore come è il lancio 5 ;. Fondamentalmente, le eccezioni possono essere qualsiasi cosa.

Detto questo, molti programmatori C ++ saranno scontenti di vedere un'eccezione che non deriva da std :: eccezione (trovato nel intestazione). Derivando tutte le eccezioni da std :: eccezione fornisce un modo per catturare eccezioni di tipo sconosciuto e recuperare informazioni da esse tramite la funzione membro membro prima di ridirle. std :: eccezione :: cosa non accetta parametri e restituisce a const char * string, che è possibile visualizzare o registrare in modo da sapere cosa ha causato l'eccezione.

Non vi è traccia di stack, senza contare le funzionalità di stack-trace fornite dal debugger, con eccezioni C ++. Poiché gli oggetti durata automatica nell'ambito del blocco try che intercetta l'eccezione vengono automaticamente eliminati prima che venga attivato il gestore di cattura appropriato, se presente, non si ha il lusso di esaminare i dati che potrebbero aver causato l'eccezione. Tutto ciò che devi lavorare inizialmente è il messaggio dalla funzione membro.

Se è facile ricreare le condizioni che hanno portato all'eccezione, è possibile impostare un punto di interruzione e rieseguire il programma, consentendoti di passare attraverso l'area del problema ed eventualmente individuare il problema. Perché non è sempre possibile, è importante essere il più precisi possibile con il messaggio di errore.

Quando deriva da std :: eccezione, dovresti assicurarti di sovrascrivere la funzione membro per fornire un utile messaggio di errore che aiuterà te e altri sviluppatori a diagnosticare cosa è andato storto.

Alcuni programmatori usano una variante di una regola che afferma che devi sempre lanciare std :: eccezione-eccezioni derivate. Ricordando che il punto di ingresso (main o wmain) restituisce un numero intero, questi programmatori genereranno std :: eccezione-derivato eccezioni quando il loro codice può recuperare, ma semplicemente getterà un valore intero ben definito se l'errore è irrecuperabile. Il codice del punto di ingresso verrà racchiuso in un blocco try che ha una cattura per un int. Il gestore catch restituirà il valore int catturato. Sulla maggior parte dei sistemi, un valore di ritorno di 0 da un programma significa successo. Qualsiasi altro valore significa fallimento.

Se si verifica un errore irreversibile, lanciare un valore intero ben definito diverso da 0 può contribuire a fornire un significato. A meno che non stiate lavorando a un progetto in cui questo è lo stile preferito, dovreste attenervisi std :: eccezione-eccezioni derivate, poiché consentono ai programmi di gestire le eccezioni utilizzando un semplice sistema di registrazione per registrare i messaggi da eccezioni non gestite e eseguono qualsiasi operazione di pulizia che sia sicura. Lanciare qualcosa che non deriva da std :: eccezione interferirebbe con questi meccanismi di registrazione degli errori.

Un'ultima cosa da notare è che il costrutto finale di C # non ha equivalenti in C ++. L'idioma RAII, se correttamente implementato, lo rende inutile poiché tutto sarà ripulito.


Eccezioni di libreria standard C ++

Abbiamo già discusso std :: eccezione, ma ci sono più tipi di quelli disponibili nella libreria standard, e c'è una funzionalità aggiuntiva da esplorare. Diamo un'occhiata alla funzionalità dal prima il file di intestazione.

Il std :: terminare funzione, per impostazione predefinita, ti consente di uscire da qualsiasi applicazione. Dovrebbe essere usato con parsimonia, dal momento che chiamarlo piuttosto che lanciare un'eccezione aggirerà tutti i normali meccanismi di gestione delle eccezioni. Se lo desideri, puoi scrivere una funzione di terminazione personalizzata senza parametri e valori di ritorno. Un esempio di questo sarà visto in ExceptionsSample, che sta arrivando.

Per impostare l'utente termina, chiami std :: set_terminate e passargli l'indirizzo della funzione. È possibile modificare il gestore di terminazione personalizzato in qualsiasi momento; l'ultimo set di funzioni è ciò che verrà chiamato nell'evento di una chiamata a std :: terminare o un'eccezione non gestita. Il gestore predefinito chiama la funzione di interruzione da file di intestazione.

Il l'intestazione fornisce una struttura rudimentale per le eccezioni. Definisce due classi che ereditano da std :: eccezione. Queste due classi servono come classe genitore per diverse altre classi.

Il std :: runtime_error class è la classe genitore per le eccezioni generate dal runtime o a causa di un errore in una funzione di libreria standard C ++. I suoi figli sono i std :: overflow_error classe, il std :: range_error classe, e il std :: underflow_error classe.

Il std :: logic_error class è la classe genitore per le eccezioni generate a causa di un errore del programmatore. I suoi figli sono i std :: domain_error classe, il std :: invalid_argument classe, il std :: length_error classe, e il std :: out_of_range classe.

È possibile derivare da queste classi o creare le proprie classi di eccezioni. Venire con una buona gerarchia di eccezioni è un compito difficile. Da un lato, vuoi eccezioni che saranno abbastanza specifiche da poter gestire tutte le eccezioni in base alle tue conoscenze in fase di build. D'altra parte, non si desidera una classe di eccezioni per ogni errore che potrebbe verificarsi. Il tuo codice finirà per essere gonfio e ingombrante, per non parlare della perdita di tempo nella scrittura di gestori di catch per ogni classe di eccezioni.

Trascorri del tempo a una lavagna, o con carta e penna, o comunque vuoi riflettere sull'albero delle eccezioni che la tua applicazione dovrebbe avere.

Il seguente esempio contiene una classe chiamata InvalidArgumentExceptionBase, che viene usato come genitore di una classe template chiamata InvalidArgumentException. La combinazione di una classe base, che può essere catturata con un gestore di eccezioni e una classe template, che ci consente di personalizzare la diagnostica di output in base al tipo di parametro, è un'opzione per il bilanciamento tra specializzazione e codice bloat.

La classe template potrebbe sembrare confusa al momento; discuteremo i modelli in un capitolo imminente, a questo punto dovrebbe chiarire qualsiasi cosa attualmente non chiara.

Esempio: ExceptionsSample \ InvalidArgumentException.h

#pragma once #include  #includere  #includere  #includere  namespace CppForCsExceptions class InvalidArgumentExceptionBase: public std :: invalid_argument public: InvalidArgumentExceptionBase (void): std :: invalid_argument ("")  virtual ~ InvalidArgumentExceptionBase (void) throw ()  virtual const char * what (void) const throw () override = 0; ; modello  class InvalidArgumentException: public InvalidArgumentExceptionBase public: inline InvalidArgumentException (const char * className, const char * functionSignature, const char * parameterName, T parameterValue); inline virtual ~ InvalidArgumentException (void) throw (); const char virtuale in linea * what (void) const throw () override; private: std :: string m_whatMessage; ; modello InvalidArgumentException:: InvalidArgumentException (const char * className, const char * functionSignature, const char * parameterName, T parameterValue): InvalidArgumentExceptionBase (), m_whatMessage () std :: stringstream msg; msg << className << "::" << functionSignature << " - parameter '" << parameterName << "' had invalid value '" << parameterValue << "'."; m_whatMessage = std::string(msg.str());  template InvalidArgumentException:: ~ InvalidArgumentException (void) throw ()  modello const char * InvalidArgumentException:: what (void) const throw () return m_whatMessage.c_str (); 

Esempio: ExceptionsSample \ ExceptionsSample.cpp

#includere  #includere  #includere  #includere  #includere  #includere  #includere  #includere  #include "InvalidArgumentException.h" #include "... /pchar.h" utilizzando namespace CppForCsExceptions; usando lo spazio dei nomi std; class ThrowClass public: ThrowClass (void): m_shouldThrow (false) wcout << L"Constructing ThrowClass." << endl;  explicit ThrowClass(bool shouldThrow) : m_shouldThrow(shouldThrow)  wcout << L"Constructing ThrowClass. shouldThrow = " << (shouldThrow ? L"true." : L"false.") << endl; if (shouldThrow)  throw InvalidArgumentException("ThrowClass", "ThrowClass (bool shouldThrow)", "shouldThrow", "true");  ~ ThrowClass (void) wcout << L"Destroying ThrowClass." << endl;  const wchar_t* GetShouldThrow(void) const  return (m_shouldThrow ? L"True" : L"False");  private: bool m_shouldThrow; ; class RegularClass  public: RegularClass(void)  wcout << L"Constructing RegularClass." << endl;  ~RegularClass(void)  wcout << L"Destroying RegularClass." << endl;  ; class ContainStuffClass  public: ContainStuffClass(void) : m_regularClass(new RegularClass()), m_throwClass(new ThrowClass())  wcout << L"Constructing ContainStuffClass." << endl;  ContainStuffClass(const ContainStuffClass& other) : m_regularClass(new RegularClass(*other.m_regularClass)), m_throwClass(other.m_throwClass)  wcout << L"Copy constructing ContainStuffClass." << endl;  ~ContainStuffClass(void)  wcout << L"Destroying ContainStuffClass." << endl;  const wchar_t* GetString(void) const  return L"I'm a ContainStuffClass.";  private: unique_ptr m_regularClass; shared_ptr m_throwClass; ; void TerminateHandler (void) wcout << L"Terminating due to unhandled exception." << endl; // If you call abort (from ), il programma uscirà // in modo anomalo. Uscirà anche in modo anomalo se non si chiama // nulla per farlo uscire da questo metodo. abort (); //// Se invece dovessi chiamare exit (0) (anche da ), //// allora il tuo programma uscirà come se nulla fosse andato a finire ////. Questo è male perché qualcosa è andato storto. //// Presento questo in modo da sapere che è possibile che //// un programma lanci un'eccezione non rilevata e comunque //// esca in un modo che non viene interpretato come un arresto anomalo, poiché //// potrebbe essere necessario scoprire perché un programma continua improvvisamente //// uscire ancora non si blocca. Questa sarebbe una di queste cause //// per quello. // exit (0);  int _pmain (int / * argc * /, _pchar * / * argv * / []) // Imposta un gestore personalizzato per std :: terminate. Si noti che questo gestore // non verrà eseguito a meno che non venga eseguito da un prompt dei comandi. Il debugger // intercetterà l'eccezione non gestita e presenterà // le opzioni di debug quando lo esegui da Visual Studio. set_terminate (& TerminateHandler); prova ContainStuffClass cSC; wcout << cSC.GetString() << endl; ThrowClass tC(false); wcout << L"tC should throw? " << tC.GetShouldThrow() << endl; tC = ThrowClass(true); wcout << L"tC should throw? " << tC.GetShouldThrow() << endl;  // One downside to using templates for exceptions is that you need a // catch handler for each specialization, unless you have a base // class they all inherit from, that is. To avoid catching // other std::invalid_argument exceptions, we created an abstract // class called InvalidArgumentExceptionBase, which serves solely to // act as the base class for all specializations of // InvalidArgumentException. Ora possiamo prenderli tutti, se lo si desidera, // senza bisogno di un gestore catch per ciascuno. Se lo volessi, tuttavia, // potresti ancora avere un gestore per una particolare specializzazione. catch (InvalidArgumentExceptionBase & e) wcout << L"Caught '" << typeid(e).name() << L"'." << endl << L"Message: " << e.what() << endl;  // Catch anything derived from std::exception that doesn't already // have a specialized handler. Since you don't know what this is, you // should catch it, log it, and re-throw it. catch (std::exception& e)  wcout << L"Caught '" << typeid(e).name() << L"'." << endl << L"Message: " << e.what() << endl; // Just a plain throw statement like this is a re-throw. throw;  // This next catch catches everything, regardless of type. Like // catching System.Exception, you should only catch this to // re-throw it. catch (… )  wcout << L"Caught unknown exception type." << endl; throw;  // This will cause our custom terminate handler to run. wcout << L"tC should throw? " << ThrowClass(true).GetShouldThrow() << endl; return 0; 

Anche se lo menziono nei commenti, volevo solo sottolineare che non vedrete la funzione di terminazione personalizzata eseguita a meno che non si esegua questo esempio da un prompt dei comandi. Se lo si esegue in Visual Studio, il debugger intercetterà il programma e orchestrerà la propria terminazione dopo aver dato la possibilità di esaminare lo stato per vedere se è possibile determinare cosa è andato storto. Inoltre, si noti che questo programma si arresta sempre in modo anomalo. Questo è in base alla progettazione poiché consente di visualizzare il gestore terminato in azione.

Conclusione

Come abbiamo visto in questo articolo, RAII aiuta a garantire il rilascio delle risorse, senza che si verifichino eccezioni, semplicemente utilizzando gli oggetti di durata di archiviazione automatica che contengono le risorse. Nella prossima puntata di questa serie, ingrandiremo puntatori e riferimenti.

Questa lezione rappresenta un capitolo di C ++, un eBook gratuito del team di Syncfusion.