C ++ supporta blocchi ‘finalmente’? (E qual è questo ‘RAII’ di cui continuo a parlare?)

C ++ supporta blocchi ‘ finalmente ‘?

Qual è l’ idioma RAII ?

Qual è la differenza tra l’idioma RAII di C ++ e l’ istruzione “using” di C # ?

No, C ++ non supporta i blocchi ‘finally’. Il motivo è che C ++ supporta invece RAII: “Acquisizione risorse è inizializzazione” – un nome scarso per un concetto davvero utile.

L’idea è che il distruttore di un object è responsabile della liberazione delle risorse. Quando l’object ha una durata di archiviazione automatica, il distruttore dell’object verrà chiamato quando il blocco in cui è stato creato si chiude, anche quando tale blocco viene chiuso in presenza di un’eccezione. Ecco la spiegazione dell’argomento di Bjarne Stroustrup .

Un uso comune di RAII è il blocco di un mutex:

// A class with implements RAII class lock { mutex &m_; public: lock(mutex &m) : m_(m) { m.acquire(); } ~lock() { m_.release(); } }; // A class which uses 'mutex' and 'lock' objects class foo { mutex mutex_; // mutex for locking 'foo' object public: void bar() { lock scopeLock(mutex_); // lock object. foobar(); // an operation which may throw an exception // scopeLock will be destructed even if an exception // occurs, which will release the mutex and allow // other functions to lock the object and run. } }; 

RAII semplifica anche l’uso di oggetti come membri di altre classi. Quando la class proprietaria viene distrutta, la risorsa gestita dalla class RAII viene rilasciata perché viene chiamato il distruttore per la class gestita da RAII. Ciò significa che quando si utilizza RAII per tutti i membri di una class che gestiscono le risorse, è ansible utilizzare un distruttore molto semplice, forse anche predefinito, per la class del proprietario poiché non è necessario gestire manualmente le relative durate delle risorse dei membri . (Grazie a Mike B per averlo indicato.)

Per coloro che hanno familiarità con C # o VB.NET, si può riconoscere che RAII è simile alla distruzione deterministica di .NET usando istruzioni IDisposable e ‘using’ . In effetti, i due metodi sono molto simili. La principale differenza è che RAII rilascerà in modo deterministico qualsiasi tipo di risorsa, inclusa la memoria. Quando si implementa IDisposable in .NET (anche nel linguaggio .NET C ++ / CLI), le risorse verranno rilasciate in modo deterministico tranne che per la memoria. In .NET, la memoria non viene rilasciata in modo deterministico; la memoria viene rilasciata solo durante i cicli di garbage collection.

† Alcune persone credono che “La distruzione è l’abbandono delle risorse” è un nome più accurato per l’idioma RAII.

In C ++, infine, NON è richiesto a causa di RAII.

RAII trasferisce la responsabilità dell’eccezione della sicurezza dall’utilizzatore dell’object al progettista (e all’implementatore) dell’object. Direi che questo è il posto giusto in quanto è solo necessario ottenere una volta un’eccezione di sicurezza corretta (nella progettazione / implementazione). Utilizzando finalmente è necessario ottenere la sicurezza delle eccezioni corretta ogni volta che si utilizza un object.

Anche IMO il codice sembra più ordinato (vedi sotto).

Esempio:

Un object di database. Per assicurarsi che la connessione DB sia utilizzata, deve essere aperta e chiusa. Usando RAII questo può essere fatto nel costruttore / distruttore.

C ++ come RAII

 void someFunc() { DB db("DBDesciptionString"); // Use the db object. } // db goes out of scope and destructor closes the connection. // This happens even in the presence of exceptions. 

L’uso di RAII rende molto semplice l’utilizzo di un object DB. L’object DB si chiuderà correttamente con l’uso di un distruttore, indipendentemente da come lo cerchiamo e lo abusiamo.

Java piace alla fine

 void someFunc() { DB db = new DB("DBDesciptionString"); try { // Use the db object. } finally { // Can not rely on finaliser. // So we must explicitly close the connection. try { db.close(); } catch(Throwable e) { /* Ignore */ // Make sure not to throw exception if one is already propagating. } } } 

Quando si utilizza infine l’uso corretto dell’object è delegato all’utente dell’object. vale a dire che è responsabilità dell’utente dell’object chiudere correttamente la connessione DB. Ora si potrebbe sostenere che questo può essere fatto nel finalizzatore, ma le risorse possono avere disponibilità limitata o altri vincoli e quindi generalmente si desidera controllare il rilascio dell’object e non fare affidamento sul comportamento non deterministico del garbage collector.

Anche questo è un semplice esempio.
Quando si dispone di più risorse che devono essere rilasciate, il codice può diventare complicato.

Un’analisi più dettagliata può essere trovata qui: http://accu.org/index.php/journals/236

In C ++ 11, se necessario, RAII consente di fare finalmente un:

 namespace detail { //adapt to your "private" namespace template  struct FinalAction { FinalAction(F f) : clean_{f} {} ~FinalAction() { if(enabled_) clean_(); } void disable() { enabled_ = false; }; private: F clean_; bool enabled_{true}; }; } template  detail::FinalAction finally(F f) { return detail::FinalAction(f); } 

esempio di utilizzo:

 #include  int main() { int* a = new int; auto delete_a = finally([a] { delete a; std::cout << "leaving the block, deleting a!\n"; }); std::cout << "doing something ...\n"; } 

l'output sarà:

 doing something... leaving the block, deleting a! 

Personalmente l'ho usato poche volte per assicurarmi di chiudere il descrittore di file POSIX in un programma C ++.

Avere una vera class che gestisca le risorse e quindi evita qualsiasi tipo di perdite di solito è meglio, ma questo è finalmente utile nei casi in cui fare una lezione suona come un eccesso.

Inoltre, mi piace meglio delle altre lingue, perché se usato naturalmente scrivi il codice di chiusura vicino al codice di apertura (nel mio esempio il nuovo e cancella ) e la distruzione segue la costruzione in ordine LIFO come al solito in C ++. L'unico svantaggio è che ottieni una variabile automatica che non usi veramente e la syntax lambda lo rende un po 'rumoroso (nel mio esempio nella quarta riga solo la parola infine e il {} -blocco a destra sono significativi, la il rest è essenzialmente rumore).

Un altro esempio:

  [...] auto precision = std::cout.precision(); auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } ); std::cout << std::setprecision(3); 

Il membro disabilitato è utile se alla fine deve essere chiamato solo in caso di errore. Ad esempio, devi copiare un object in tre contenitori diversi, puoi finalmente impostare l'annullamento di ogni copia e distriggersre dopo che tutte le copie hanno avuto successo. In questo modo, se la distruzione non può essere generata, garantisci la forte garanzia.

disabilita esempio:

 //strong guarantee void copy_to_all(BIGobj const& a) { first_.push_back(a); auto undo_first_push = finally([first_&] { first_.pop_back(); }); second_.push_back(a); auto undo_second_push = finally([second_&] { second_.pop_back(); }); third_.push_back(a); //no necessary, put just to make easier to add containers in the future auto undo_third_push = finally([third_&] { third_.pop_back(); }); undo_first_push.disable(); undo_second_push.disable(); undo_third_push.disable(); } 

Oltre a semplificare la pulizia degli oggetti basati su stack, RAII è utile anche perché la stessa pulizia “automatica” avviene quando l’object è membro di un’altra class. Quando la class proprietaria viene distrutta, la risorsa gestita dalla class RAII viene ripulita perché viene chiamato il dtor per quella class.

Ciò significa che quando raggiungi RAI nirvana e tutti i membri di una class usano RAII (come puntatori intelligenti), puoi ottenere un dtor molto semplice (forse anche predefinito) per la class owner dal momento che non ha bisogno di gestirlo manualmente durata delle risorse dei membri.

perché persino le lingue gestite sono in grado di fornire un blocco definitivo nonostante le risorse vengano automaticamente ridistribuite dal garbage collector?

In realtà, le lingue basate sui Garbage collector necessitano “finalmente” di più. Un garbage collector non distrugge i tuoi oggetti in modo tempestivo, quindi non può essere fatto affidamento per ripulire correttamente i problemi relativi alla memoria.

In termini di dati allocati dynamicmente, molti sostengono che si dovrebbero usare i puntatori intelligenti.

Però…

RAII trasferisce la responsabilità della sicurezza delle eccezioni dall’utilizzatore dell’object al progettista

Purtroppo questa è la sua stessa rovina. Le vecchie abitudini di programmazione C sono dure a morire. Quando si utilizza una libreria scritta in C o molto stile C, RAII non sarà stato utilizzato. A parte ri-scrivere l’intero front-end API, è proprio quello con cui devi lavorare. Quindi la mancanza di “finalmente” morde davvero.

Ci scusiamo per aver scavato un thread vecchio, ma c’è un grosso errore nel seguente ragionamento:

RAII trasferisce la responsabilità dell’eccezione della sicurezza dall’utilizzatore dell’object al progettista (e all’implementatore) dell’object. Direi che questo è il posto giusto in quanto è solo necessario ottenere una volta un’eccezione di sicurezza corretta (nella progettazione / implementazione). Utilizzando finalmente è necessario ottenere la sicurezza delle eccezioni corretta ogni volta che si utilizza un object.

Più spesso, devi occuparti di oggetti allocati dynamicmente, numeri dinamici di oggetti, ecc. All’interno del try-block, alcuni codici potrebbero creare molti oggetti (quanti sono determinati in fase di runtime) e memorizzare puntatori in una lista. Ora, questo non è uno scenario esotico, ma molto comune. In questo caso, vorresti scrivere cose del genere

 void DoStuff(vector input) { list myList; try { for (int i = 0; i < input.size(); ++i) { Foo* tmp = new Foo(input[i]); if (!tmp) throw; myList.push_back(tmp); } DoSomeStuff(myList); } finally { while (!myList.empty()) { delete myList.back(); myList.pop_back(); } } } 

Ovviamente la lista stessa verrà distrutta quando si esce dal campo di applicazione, ma ciò non eliminerebbe gli oggetti temporanei che sono stati creati.

Invece, devi seguire la brutta strada:

 void DoStuff(vector input) { list myList; try { for (int i = 0; i < input.size(); ++i) { Foo* tmp = new Foo(input[i]); if (!tmp) throw; myList.push_back(tmp); } DoSomeStuff(myList); } catch(...) { } while (!myList.empty()) { delete myList.back(); myList.pop_back(); } } 

Inoltre: perché persino i managed lanuages ​​forniscono un blocco definitivo nonostante le risorse vengano automaticamente ridistribuite dal garbage collector?

Suggerimento: c'è più che puoi fare con "finalmente" che con la semplice distribuzione della memoria.

FWIW, Microsoft Visual C ++ supporta try, infine, è stato storicamente utilizzato nelle app MFC come metodo per rilevare gravi eccezioni che altrimenti causerebbero un arresto anomalo. Per esempio;

 int CMyApp::Run() { __try { int i = CWinApp::Run(); m_Exitok = MAGIC_EXIT_NO; return i; } __finally { if (m_Exitok != MAGIC_EXIT_NO) FaultHandler(); } } 

Ho usato questo in passato per fare cose come salvare i backup dei file aperti prima di uscire. Alcune impostazioni di debug del JIT interromperanno comunque questo meccanismo.

Non proprio, ma è ansible emularli in qualche modo, ad esempio:

 int * array = new int[10000000]; try { // Some code that can throw exceptions // ... throw std::exception(); // ... } catch (...) { // The finally-block (if an exception is thrown) delete[] array; // re-throw the exception. throw; } // The finally-block (if no exception was thrown) delete[] array; 

Si noti che il blocco finally potrebbe lanciare un’eccezione prima che l’eccezione originale venga nuovamente lanciata, eliminando in tal modo l’eccezione originale. Questo è esattamente lo stesso comportamento di un blocco finally di Java. Inoltre, non è ansible utilizzare il return all’interno dei blocchi try & catch.

Ho trovato una macro finally utilizzabile quasi come la parola chiave finally in Java; fa uso di std::exception_ptr e amici, funzioni lambda e std::promise , quindi richiede C++11 o successivo; fa anche uso dell’estensione GCC di espressioni composte , che è anche supportata da clang.

ATTENZIONE : una versione precedente di questa risposta utilizzava una diversa implementazione del concetto con molte più limitazioni.

Per prima cosa, definiamo una class helper.

 #include  template  class FinallyHelper { template  struct TypeWrapper {}; using Return = typename std::result_of::type; public: FinallyHelper(Fun body) { try { execute(TypeWrapper(), body); } catch(...) { m_promise.set_exception(std::current_exception()); } } Return get() { return m_promise.get_future().get(); } private: template  void execute(T, Fun body) { m_promise.set_value(body()); } void execute(TypeWrapper, Fun body) { body(); } std::promise m_promise; }; template  FinallyHelper make_finally_helper(Fun body) { return FinallyHelper(body); } 

Poi c’è la macro attuale.

 #define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try #define finally }); \ true; \ ({return __finally_helper.get();})) \ /***/ 

Può essere usato in questo modo:

 void test() { try_with_finally { raise_exception(); } catch(const my_exception1&) { /*...*/ } catch(const my_exception2&) { /*...*/ } finally { clean_it_all_up(); } } 

L’uso di std::promise rende molto facile da implementare, ma probabilmente introduce anche un po ‘di overhead non necessario che potrebbe essere evitato reimplementando solo le funzionalità necessarie da std::promise .


¹ CAVEAT: ci sono alcune cose che non funzionano abbastanza come la versione java di finally . Fuori dalla mia testa:

  1. non è ansible uscire da un ciclo esterno con l’istruzione break dai blocchi try e catch() , poiché vivono all’interno di una funzione lambda;
  2. ci deve essere almeno un blocco catch() dopo il try : è un requisito C ++;
  3. se la funzione ha un valore di ritorno diverso da void ma non c’è ritorno tra catch()'s blocchi try and catch()'s , la compilazione fallirà perché la macro finally si espanderà al codice che vorrà restituire un void . Questo potrebbe essere, errare, un vuoto ed avendo una finally_noreturn macro finally_noreturn .

Tutto sumto, non so se userò mai questa roba da solo, ma è stato divertente giocarci. 🙂

Ho un caso d’uso in cui penso che alla finally dovrebbe essere una parte perfettamente accettabile del linguaggio C ++ 11, poiché penso che sia più facile da leggere dal punto di vista del stream. Il mio caso d’uso è una catena di fili consumatore / produttore, in cui un sentinel nullptr viene inviato alla fine dell’esecuzione per arrestare tutti i thread.

Se C ++ lo supporta, vorresti che il tuo codice assomigli a questo:

  extern Queue downstream, upstream; int Example() { try { while(!ExitRequested()) { X* x = upstream.pop(); if (!x) break; x->doSomething(); downstream.push(x); } } finally { downstream.push(nullptr); } } 

Penso che sia più logico mettere la tua dichiarazione finale all’inizio del ciclo, poiché si verifica dopo che il ciclo è terminato … ma questo è un pio desiderio perché non possiamo farlo in C ++. Si noti che la coda downstream è connessa ad un altro thread, quindi non è ansible inserire la sentinella sentinella push(nullptr) nel distruttore di downstream perché non può essere distrutta a questo punto … deve rimanere in vita fino a quando l’altra thread riceve il nullptr .

Quindi ecco come usare una class RAII con lambda per fare lo stesso:

  class Finally { public: Finally(std::function callback) : callback_(callback) { } ~Finally() { callback_(); } std::function callback_; }; 

ed ecco come lo usi:

  extern Queue downstream, upstream; int Example() { Finally atEnd([](){ downstream.push(nullptr); }); while(!ExitRequested()) { X* x = upstream.pop(); if (!x) break; x->doSomething(); downstream.push(x); } } 

Un’altra emulazione di blocco “finally” usando le funzioni lambda C ++ 11

 template  inline void with_finally(const TCode &code, const TFinallyCode &finally_code) { try { code(); } catch (...) { try { finally_code(); } catch (...) // Maybe stupid check that finally_code mustn't throw. { std::terminate(); } throw; } finally_code(); } 

Speriamo che il compilatore ottimizzi il codice sopra.

Ora possiamo scrivere un codice come questo:

 with_finally( [&]() { try { // Doing some stuff that may throw an exception } catch (const exception1 &) { // Handling first class of exceptions } catch (const exception2 &) { // Handling another class of exceptions } // Some classs of exceptions can be still unhandled }, [&]() // finally { // This code will be executed in all three cases: // 1) exception was not thrown at all // 2) exception was handled by one of the "catch" blocks above // 3) exception was not handled by any of the "catch" block above } ); 

Se desideri, puoi inserire questo idioma in macro “try – finally”:

 // Please never throw exception below. It is needed to avoid a compilation error // in the case when we use "begin_try ... finally" without any "catch" block. class never_thrown_exception {}; #define begin_try with_finally([&](){ try #define finally catch(never_thrown_exception){throw;} },[&]() #define end_try ) // sorry for "pascalish" style :( 

Ora il blocco “finally” è disponibile in C ++ 11:

 begin_try { // A code that may throw } catch (const some_exception &) { // Handling some exceptions } finally { // A code that is always executed } end_try; // Sorry again for this ugly thing 

Personalmente non mi piace la versione “macro” dell’idioma “finally” e preferirei usare la pura funzione “with_finally” anche se una syntax è più ingombrante in quel caso.

Puoi testare il codice qui sopra: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813

Come molte persone hanno affermato, la soluzione è utilizzare le funzionalità di C ++ 11 per evitare blocchi definitivi. Una delle funzionalità è unique_ptr .

Ecco la risposta di Mephane scritta usando i modelli RAII.

 #include  #include  #include  using namespace std; class Foo { ... }; void DoStuff(vector input) { list > myList; for (int i = 0; i < input.size(); ++i) { myList.push_back(unique_ptr(new Foo(input[i]))); } DoSomeStuff(myList); } 

Qualche altra introduzione all’uso di unique_ptr con i contenitori della libreria standard C ++ è qui

Mi piacerebbe fornire un’alternativa.

Se vuoi che il blocco venga chiamato sempre, basta metterlo dopo l’ultimo blocco catch (che probabilmente dovrebbe essere catch( ... ) per catturare l’eccezione non nota)

 try{ // something that might throw exception } catch( ... ){ // what to do with uknown exception } //final code to be called always, //don't forget that it might throw some exception too doSomeCleanUp(); 

Se vuoi bloccare definitivamente come ultima cosa da fare quando viene lanciata un’eccezione puoi usare la variabile locale booleana – prima di eseguirla devi impostarla su false e mettere un’assegnazione vera alla fine del blocco try, quindi dopo aver catturato il blocco controlla la variabile valore:

 bool generalAppState = false; try{ // something that might throw exception //the very end of try block: generalAppState = true; } catch( ... ){ // what to do with uknown exception } //final code to be called only when exception was thrown, //don't forget that it might throw some exception too if( !generalAppState ){ doSomeCleanUpOfDirtyEnd(); } //final code to be called only when no exception is thrown //don't forget that it might throw some exception too else{ cleanEnd(); } 

Come indicato nelle altre risposte, C ++ può supportare funzionalità come la finally . L’implementazione di questa funzionalità che è probabilmente la più vicina all’essere parte della lingua standard è quella che accompagna le linee guida C ++ Core , un insieme di best practice per l’uso del C ++ curato da Bjarne Stoustrup e Herb Sutter. Questa implementazione di fa finally parte della Guidelines Support Library (GSL). In tutte le linee guida, l’uso di finally è raccomandato quando si ha a che fare con interfacce vecchio stile, e ha anche una propria linea guida, intitolato Use a final_action object per esprimere cleanup se non è disponibile un handle di risorsa adatto .

Quindi, non solo il supporto per C ++ è finally , è consigliabile utilizzarlo in molti casi d’uso comuni.

Un esempio di utilizzo dell’implementazione GSL potrebbe essere:

 #include  void example() { int *p = get_some_resource(); auto p_clean = gsl::finally([&p] { clean_that_resource(p); }); // Do a lot of stuff, return early and throw exceptions. // clean_that_resource will always get called. } 

L’implementazione e l’utilizzo di GSL è molto simile a quello della risposta di Paolo.Bolzoni . Una differenza è che l’object creato da gsl::finally() non ha la chiamata disable() . Se hai bisogno di quella funzionalità, potresti preferire l’implementazione di Paolo. In caso contrario, l’utilizzo di GSL è il più vicino all’utilizzo di funzionalità standardizzate.

 try { ... goto finally; } catch(...) { ... goto finally; } finally: { ... }