Il C ++ supporta i blocchi " finalmente "?
Qual è il linguaggio RAII ?
Qual è la differenza tra il linguaggio RAII di C ++ e l' istruzione 'using' di C # ?
Il C ++ supporta i blocchi " finalmente "?
Qual è il linguaggio RAII ?
Qual è la differenza tra il linguaggio RAII di C ++ e l' istruzione 'using' di C # ?
Risposte:
No, C ++ non supporta i blocchi "finalmente". Il motivo è che C ++ invece supporta RAII: "L'acquisizione delle risorse è inizializzazione" - un nome scadente † per un concetto davvero utile.
L'idea è che il distruttore di un oggetto sia responsabile della liberazione delle risorse. Quando l'oggetto ha una durata di memorizzazione automatica, il distruttore dell'oggetto verrà chiamato quando il blocco in cui è stato creato esce - anche quando quel blocco viene chiuso in presenza di un'eccezione. Ecco la spiegazione di Bjarne Stroustrup sull'argomento.
Un uso comune per 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 classe proprietaria "viene distrutta, la risorsa gestita dalla classe RAII viene rilasciata perché di conseguenza viene chiamato il distruttore per la classe gestita RAII. Ciò significa che quando si utilizza RAII per tutti i membri di una classe che gestiscono risorse, è possibile cavarsela utilizzando un distruttore molto semplice, forse anche predefinito, per la classe proprietaria poiché non è necessario gestire manualmente la durata delle risorse dei membri . (Grazie a Mike B per averlo segnalato.)
Per coloro che hanno familiarità con C # o VB.NET, potresti riconoscere che RAII è simile alla distruzione deterministica di .NET usando le istruzioni IDisposable e "using" . In effetti, i due metodi sono molto simili. La differenza principale è che RAII rilascerà in modo deterministico qualsiasi tipo di risorsa, inclusa la memoria. Quando si implementa IDisposable in .NET (anche il linguaggio .NET C ++ / CLI), le risorse verranno rilasciate in modo deterministico ad eccezione della 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 sia rinuncia alle risorse" è un nome più accurato per il linguaggio RAII.
In C ++, infine, NON è richiesto a causa di RAII.
RAII sposta la responsabilità della sicurezza delle eccezioni dall'utente dell'oggetto al progettista (e implementatore) dell'oggetto. Direi che questo è il posto giusto in quanto è necessario solo una volta corretta la sicurezza delle eccezioni (nella progettazione / implementazione). Usando finalmente è necessario ottenere la sicurezza delle eccezioni corretta ogni volta che si utilizza un oggetto.
Anche IMO il codice sembra più ordinato (vedi sotto).
Esempio:
Un oggetto di database. Per assicurarsi che la connessione DB sia utilizzata, deve essere aperta e chiusa. Usando RAII questo può essere fatto nel costruttore / distruttore.
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 corretto di un oggetto DB. L'oggetto DB si chiuderà correttamente mediante l'uso di un distruttore, indipendentemente da come lo proviamo e lo abusiamo.
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'oggetto viene delegato all'utente dell'oggetto. vale a dire l'utente dell'oggetto deve chiudere correttamente la connessione DB in modo corretto. Ora potresti sostenere che ciò può essere fatto nel finalizzatore, ma le risorse possono avere disponibilità limitata o altri vincoli e quindi in genere vuoi controllare il rilascio dell'oggetto e non fare affidamento sul comportamento non deterministico del garbage collector.
Anche questo è un semplice esempio.
Quando hai 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
// Make sure not to throw exception if one is already propagating.
È importante che i distruttori C ++ non generino eccezioni anche per questo motivo.
RAII di solito è meglio, ma puoi avere facilmente la semantica di fine in C ++. Utilizzando una piccola quantità di codice.
Inoltre, le Linee guida di base C ++ danno finalmente.
Ecco un collegamento all'implementazione Microsoft GSL e un collegamento all'implementazione Martin Moene
Bjarne Stroustrup più volte ha detto che tutto ciò che è nella GSL significava andare allo standard alla fine. Quindi dovrebbe essere un modo a prova di futuro da usare finalmente .
Puoi facilmente implementare te stesso se vuoi, continua a leggere.
In C ++ 11 RAII e lambdas consentono finalmente di fare un generale:
namespace detail { //adapt to your "private" namespace
template <typename F>
struct FinalAction {
FinalAction(F f) : clean_{f} {}
~FinalAction() { if(enabled_) clean_(); }
void disable() { enabled_ = false; };
private:
F clean_;
bool enabled_{true}; }; }
template <typename F>
detail::FinalAction<F> finally(F f) {
return detail::FinalAction<F>(f); }
esempio di utilizzo:
#include <iostream>
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 alcune volte per assicurarmi di chiudere il descrittore di file POSIX in un programma C ++.
Avere una vera classe che gestisce le risorse e quindi evita qualsiasi tipo di perdita è di solito meglio, ma questo alla fine è utile nei casi in cui fare una classe suona come un eccesso.
Inoltre, mi piace meglio di altri linguaggi , infine, perché se usato, naturalmente, si scrive il codice di chiusura vicina il codice di apertura (nel mio esempio la nuova e di eliminazione ) costruzione e distruzione seguito in ordine LIFO come al solito in C ++. L'unico aspetto negativo è che ottieni una variabile automatica che non usi davvero e la sintassi lambda la rende un po 'rumorosa (nel mio esempio nella quarta riga solo la parola finalmente e il blocco {} a destra sono significativi, il il resto è 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 di disabilitazione è utile se deve essere chiamato infine solo in caso di errore. Ad esempio, devi copiare un oggetto in tre contenitori diversi, puoi infine impostare il comando per annullare ogni copia e disabilitarla dopo che tutte le copie hanno esito positivo. In questo modo, se la distruzione non può essere lanciata, si garantisce una 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(); }
Se non è possibile utilizzare C ++ 11 è ancora possibile avere finalmente , ma il codice diventa un po 'più lungo senza fiato. Basta definire una struttura con solo un costruttore e un distruttore, il costruttore prende riferimenti a tutto ciò che è necessario e il distruttore fa le azioni necessarie. Questo è fondamentalmente ciò che fa la lambda, fatto manualmente.
#include <iostream>
int main() {
int* a = new int;
struct Delete_a_t {
Delete_a_t(int* p) : p_(p) {}
~Delete_a_t() { delete p_; std::cout << "leaving the block, deleting a!\n"; }
int* p_;
} delete_a(a);
std::cout << "doing something ...\n"; }
FinalAction
è sostanzialmente lo stesso del ScopeGuard
linguaggio popolare , solo con un nome diverso.
Oltre a semplificare la pulizia con oggetti basati su stack, RAII è utile anche perché la stessa pulizia "automatica" si verifica quando l'oggetto fa parte di un'altra classe. Quando la classe proprietaria viene distrutta, la risorsa gestita dalla classe RAII viene ripulita perché il dtor per quella classe viene chiamato di conseguenza.
Ciò significa che quando raggiungi il nirvana di RAII e tutti i membri di una classe usano RAII (come i puntatori intelligenti), puoi cavartela con un dtor molto semplice (forse anche predefinito) per la classe del proprietario poiché non è necessario gestirne manualmente durata delle risorse dei membri.
perché anche le lingue gestite forniscono un blocco finale nonostante le risorse vengano automaticamente dislocate 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 è possibile fare affidamento su come risolvere correttamente i problemi relativi alla non memoria.
In termini di dati allocati dinamicamente, molti sosterrebbero che dovresti usare gli smart-pointer.
Però...
RAII sposta la responsabilità della sicurezza delle eccezioni dall'utilizzatore dell'oggetto al progettista
Purtroppo questa è la sua rovina. Le vecchie abitudini di programmazione C sono dure a morire. Quando stai usando una libreria scritta in stile C o molto C, RAII non sarà usato. A meno di riscrivere l'intero front-end API, è proprio quello con cui devi lavorare. Quindi la mancanza di "finalmente" morde davvero.
CleanupFailedException
. Esiste un modo plausibile per ottenere questo risultato usando RAII?
SomeObject.DoSomething()
metodo e vorrà sapere se (1) è riuscito, (2) ha fallito senza effetti collaterali , (3) ha fallito con effetti collaterali che il chiamante è pronto a far fronte o (4) non riuscito con effetti collaterali che il chiamante non può far fronte. Solo il chiamante saprà quali situazioni può e non può affrontare; ciò di cui il chiamante ha bisogno è un modo per sapere qual è la situazione. È un peccato che non esista un meccanismo standard per fornire le informazioni più importanti su un'eccezione.
Un'altra emulazione di blocco "finalmente" che utilizza le funzioni lambda C ++ 11
template <typename TCode, typename TFinallyCode>
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 codice in questo modo:
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 classes 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 lo desideri, puoi avvolgere questo idioma in macro "prova - finalmente":
// 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 "finalmente" è 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" di "finalmente" idioma e preferirei usare la funzione "with_finally" pura anche se in questo caso una sintassi è più voluminosa.
Puoi testare il codice qui sopra: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813
PS
Se hai bisogno di un blocco finalmente nel tuo codice, le protezioni con ambito o le macro ON_FINALLY / ON_EXCEPTION probabilmente risponderanno meglio alle tue esigenze.
Ecco un breve esempio di utilizzo ON_FINALLY / ON_EXCEPTION:
void function(std::vector<const char*> &vector)
{
int *arr1 = (int*)malloc(800*sizeof(int));
if (!arr1) { throw "cannot malloc arr1"; }
ON_FINALLY({ free(arr1); });
int *arr2 = (int*)malloc(900*sizeof(int));
if (!arr2) { throw "cannot malloc arr2"; }
ON_FINALLY({ free(arr2); });
vector.push_back("good");
ON_EXCEPTION({ vector.pop_back(); });
...
Ci scusiamo per aver scavato un thread così vecchio, ma c'è un errore grave nel seguente ragionamento:
RAII sposta la responsabilità della sicurezza delle eccezioni dall'utente dell'oggetto al progettista (e implementatore) dell'oggetto. Direi che questo è il posto giusto in quanto è necessario solo una volta corretta la sicurezza delle eccezioni (nella progettazione / implementazione). Usando finalmente è necessario ottenere la sicurezza delle eccezioni corretta ogni volta che si utilizza un oggetto.
Più spesso, devi occuparti di oggetti allocati dinamicamente, numeri dinamici di oggetti ecc. All'interno del blocco try, alcuni codici potrebbero creare molti oggetti (quanti sono determinati in fase di esecuzione) e memorizzare i puntatori su di essi in un elenco. Ora, questo non è uno scenario esotico, ma molto comune. In questo caso, vorresti scrivere cose come
void DoStuff(vector<string> input)
{
list<Foo*> 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 l'elenco stesso verrà distrutto quando si esce dall'ambito, ma ciò non ripulirebbe gli oggetti temporanei che hai creato.
Invece, devi seguire la brutta strada:
void DoStuff(vector<string> input)
{
list<Foo*> 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é anche le lingue gestite forniscono un blocco finalmente nonostante le risorse vengano automaticamente dislocate dal garbage collector?
Suggerimento: c'è di più che puoi fare con "finalmente" oltre alla semplice deallocazione della memoria.
new
non restituisce NULL, genera invece un'eccezione
std::shared_ptr
e std::unique_ptr
direttamente nello stdlib.
FWIW, Microsoft Visual C ++ supporta try, finalmente ed è stato storicamente utilizzato nelle app MFC come metodo per rilevare gravi eccezioni che altrimenti comporterebbero 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 di file aperti prima di uscire. Tuttavia, alcune impostazioni di debug JIT interromperanno questo meccanismo.
Come sottolineato nelle altre risposte, C ++ può supportare finally
funzionalità simili. L'implementazione di questa funzionalità che è probabilmente più vicina a far parte del linguaggio standard è quella che accompagna le Linee guida di base C ++ , un insieme di migliori pratiche per l'uso del C ++ modificato da Bjarne Stoustrup e Herb Sutter. Un attuazionefinally
è parte della libreria di supporto Linee guida (GSL). In tutte le Linee guida, finally
si consiglia l' uso di quando si ha a che fare con interfacce vecchio stile e ha anche una linea guida propria, intitolata Usa un oggetto final_action per esprimere la pulizia se non è disponibile un handle di risorse adatto .
Quindi, non solo supporta il C ++ finally
, in realtà si consiglia di usarlo in molti casi d'uso comuni.
Un esempio di utilizzo dell'implementazione GSL sarebbe simile a:
#include <gsl/gsl_util.h>
void example()
{
int handle = get_some_resource();
auto handle_clean = gsl::finally([&handle] { clean_that_resource(handle); });
// 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 nella risposta di Paolo.Bolzoni . Una differenza è che l'oggetto creato dagsl::finally()
manca della disable()
chiamata. Se hai bisogno di quella funzionalità (ad esempio, per restituire la risorsa una volta assemblata e non sono previste eccezioni), potresti preferire l'implementazione di Paolo. Altrimenti, l'utilizzo di GSL è simile all'utilizzo di funzionalità standardizzate.
Non proprio, ma puoi emularli in qualche misura, 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 a sua volta generare un'eccezione prima che venga generata nuovamente l'eccezione originale, eliminando così l'eccezione originale. Questo è esattamente lo stesso comportamento di un blocco Java finalmente. Inoltre, non è possibile utilizzare return
all'interno dei blocchi try & catch.
std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
finally
blocco.
Mi è venuta in mente una finally
macro che può essere usata quasi come ¹ la finally
parola chiave in Java; fa uso di std::exception_ptr
e amici, funzioni lambda e std::promise
, quindi richiede C++11
o sopra; utilizza anche l' estensione GCC di espressione dell'istruzione composta , che è supportata anche da clang.
ATTENZIONE : una versione precedente di questa risposta utilizzava un'implementazione diversa del concetto con molte altre limitazioni.
Innanzitutto, definiamo una classe di supporto.
#include <future>
template <typename Fun>
class FinallyHelper {
template <typename T> struct TypeWrapper {};
using Return = typename std::result_of<Fun()>::type;
public:
FinallyHelper(Fun body) {
try {
execute(TypeWrapper<Return>(), body);
}
catch(...) {
m_promise.set_exception(std::current_exception());
}
}
Return get() {
return m_promise.get_future().get();
}
private:
template <typename T>
void execute(T, Fun body) {
m_promise.set_value(body());
}
void execute(TypeWrapper<void>, Fun body) {
body();
}
std::promise<Return> m_promise;
};
template <typename Fun>
FinallyHelper<Fun> make_finally_helper(Fun body) {
return FinallyHelper<Fun>(body);
}
Quindi 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
semplifica l'implementazione, ma probabilmente introduce anche un po 'di spese generali non necessarie che potrebbero essere evitate reimplementando solo le funzionalità necessarie std::promise
.
¹ CAVEAT: ci sono alcune cose che non funzionano come la versione java di finally
. In cima alla mia testa:
break
dichiarazione all'interno del try
e catch()
's blocchi, poiché vivono all'interno di una funzione lambda;catch()
blocco dopo try
: è un requisito C ++;try
e catch()'s
, la compilazione fallirà perché la finally
macro si espanderà nel codice che vorrà restituire a void
. Questo potrebbe essere, a volte, un vuoto avendo una finally_noreturn
specie di macro.Tutto sommato, non so se avrei mai usato queste cose da solo, ma è stato divertente giocarci. :)
catch(xxx) {}
blocco impossibile all'inizio della finally
macro, dove xxx è un tipo falso solo allo scopo di avere almeno un blocco catch.
catch(...)
, no?
xxx
in uno spazio dei nomi privato che non verrà mai utilizzato.
Ho un caso d'uso in cui penso che finally
dovrebbe essere una parte perfettamente accettabile del linguaggio C ++ 11, poiché penso che sia più facile da leggere dal punto di vista del flusso. Il mio caso d'uso è una catena di thread consumatore / produttore, in cui una sentinella nullptr
viene inviata alla fine della corsa per chiudere tutti i thread.
Se C ++ lo supportasse, il tuo codice dovrebbe apparire così:
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 questo sia più logico che mettere la tua dichiarazione finalmente all'inizio del ciclo, dal momento che si verifica dopo che il ciclo è uscito ... ma questo è un pio desiderio perché non possiamo farlo in C ++. Nota che la coda downstream
è connessa a un altro thread, quindi non puoi inserire la sentinella push(nullptr)
nel distruttore downstream
perché non può essere distrutta a questo punto ... deve rimanere in vita fino a quando l'altro thread non riceve il nullptr
.
Ecco quindi come utilizzare una classe RAII con lambda per fare lo stesso:
class Finally
{
public:
Finally(std::function<void(void)> callback) : callback_(callback)
{
}
~Finally()
{
callback_();
}
std::function<void(void)> 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);
}
}
Come molte persone hanno affermato, la soluzione è utilizzare le funzionalità C ++ 11 per evitare finalmente i blocchi. Una delle caratteristiche è unique_ptr
.
Ecco la risposta di Mephane scritta usando modelli RAII.
#include <vector>
#include <memory>
#include <list>
using namespace std;
class Foo
{
...
};
void DoStuff(vector<string> input)
{
list<unique_ptr<Foo> > myList;
for (int i = 0; i < input.size(); ++i)
{
myList.push_back(unique_ptr<Foo>(new Foo(input[i])));
}
DoSomeStuff(myList);
}
Ancora un po 'introduzione all'uso unique_ptr con i contenitori C ++ Standard Library è qui
Vorrei fornire un'alternativa.
Se vuoi che il blocco finalmente sia chiamato sempre, basta inserirlo dopo l'ultimo blocco catch (che probabilmente dovrebbe essere catch( ... )
quello di catturare un'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 infine bloccare come ultima cosa da fare quando viene generata un'eccezione, puoi utilizzare la variabile locale booleana: prima di eseguirla, imposta su false e imposta l'assegnazione vera alla fine del blocco try, quindi dopo il blocco catch verifica 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();
}
Penso anche che RIIA non sia un sostituto del tutto utile per la gestione delle eccezioni e per avere finalmente. A proposito, penso anche che RIIA sia un brutto nome dappertutto. Chiamo questi tipi di classi "bidelli" e li uso MOLTO. Il 95% delle volte non sta inizializzando né acquisendo risorse, sta applicando alcune modifiche su una base mirata o prendendo qualcosa di già impostato e assicurandosi che sia distrutto. Essendo questo il nome ufficiale del modello ossessionato da Internet, mi viene abusato anche solo del fatto che il mio nome potrebbe essere migliore.
Non penso sia ragionevole richiedere che ogni complicata configurazione di un elenco di cose ad hoc debba avere una classe scritta per contenerla al fine di evitare complicazioni quando si ripulisce il tutto di fronte alla necessità di catturare più tipi di eccezione se qualcosa va storto nel processo. Ciò porterebbe a molte classi ad hoc che altrimenti non sarebbero necessarie.
Sì, va bene per le classi progettate per gestire una particolare risorsa o per quelle generiche progettate per gestire un insieme di risorse simili. Ma, anche se tutte le cose coinvolte hanno tali involucri, il coordinamento della pulizia potrebbe non essere solo una semplice invocazione in ordine inverso dei distruttori.
Penso che abbia perfettamente senso avere finalmente un C ++. Voglio dire, accidenti, così tanti pezzi e bob sono stati incollati su di esso negli ultimi decenni che sembra che strane persone diventino improvvisamente conservatrici su qualcosa come finalmente che potrebbe essere abbastanza utile e probabilmente niente di così complicato come alcune altre cose che sono state aggiunto (anche se è solo una supposizione da parte mia).
try
{
...
goto finally;
}
catch(...)
{
...
goto finally;
}
finally:
{
...
}
finally
non funziona.