Ci sono ottime risposte là fuori, quindi aggiungo solo alcune cose dimenticate.
0. RAII riguarda gli scopi
RAII riguarda entrambi:
- acquisire una risorsa (non importa quale risorsa) nel costruttore e annullarne l'acquisizione nel distruttore.
- avere il costruttore eseguito quando la variabile viene dichiarata e il distruttore eseguito automaticamente quando la variabile esce dall'ambito.
Altri hanno già risposto a questo, quindi non approfondirò.
1. Quando si codifica in Java o C #, si utilizza già RAII ...
MONSIEUR JOURDAIN: Cosa! Quando dico: "Nicole, portami le mie pantofole e dammi il berretto da notte", è prosa?
MAESTRO DI FILOSOFIA: Sì, signore.
MONSIEUR JOURDAIN: Da più di quarant'anni parlo di prosa senza saperlo, e ti sono molto grato per avermelo insegnato.
- Molière: Il gentiluomo della classe media, atto 2, scena 4
Come ha fatto Monsieur Jourdain con la prosa, C # e persino Java usano già RAII, ma in modi nascosti. Ad esempio, il seguente codice Java (che viene scritto allo stesso modo in C # sostituendolo synchronized
con lock
):
void foo()
{
// etc.
synchronized(someObject)
{
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
... sta già utilizzando RAII: l'acquisizione del mutex viene eseguita nella parola chiave ( synchronized
o lock
) e l'annullamento dell'acquisizione verrà eseguita quando si esce dall'ambito.
È così naturale nella sua notazione che non richiede quasi nessuna spiegazione anche per le persone che non hanno mai sentito parlare di RAII.
Il vantaggio che C ++ ha su Java e C # qui è che tutto può essere fatto usando RAII. Ad esempio, non ci sono equivalenti incorporati diretti synchronized
né lock
in C ++, ma possiamo ancora averli.
In C ++, sarebbe scritto:
void foo()
{
// etc.
{
Lock lock(someObject) ; // lock is an object of type Lock whose
// constructor acquires a mutex on
// someObject and whose destructor will
// un-acquire it
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
che può essere facilmente scritto in Java / C # (utilizzando le macro C ++):
void foo()
{
// etc.
LOCK(someObject)
{
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
2. RAII ha usi alternativi
CONIGLIO BIANCO: [cantando] Sono in ritardo / Sono in ritardo / Per un appuntamento molto importante. / Non c'è tempo per dire "Ciao". / Addio. / Sono in ritardo, sono in ritardo, sono in ritardo.
- Alice nel paese delle meraviglie (versione Disney, 1951)
Sai quando verrà chiamato il costruttore (alla dichiarazione dell'oggetto) e sai quando verrà chiamato il suo corrispondente distruttore (all'uscita dello scope), quindi puoi scrivere codice quasi magico con una sola riga. Benvenuti nel paese delle meraviglie del C ++ (almeno, dal punto di vista di uno sviluppatore C ++).
Ad esempio, puoi scrivere un oggetto contatore (l'ho lasciato come esercizio) e usarlo semplicemente dichiarando la sua variabile, come è stato usato l'oggetto lock sopra:
void foo()
{
double timeElapsed = 0 ;
{
Counter counter(timeElapsed) ;
// do something lengthy
}
// now, the timeElapsed variable contain the time elapsed
// from the Counter's declaration till the scope exit
}
che ovviamente può essere scritto, ancora una volta, in Java / C # usando una macro:
void foo()
{
double timeElapsed = 0 ;
COUNTER(timeElapsed)
{
// do something lengthy
}
// now, the timeElapsed variable contain the time elapsed
// from the Counter's declaration till the scope exit
}
3. Perché manca il C ++ finally
?
[GRIDANDO] È il conto alla rovescia finale !
- Europa: The Final Countdown (scusa, avevo esaurito le quotazioni, qui ... :-)
La finally
clausola viene utilizzata in C # / Java per gestire l'eliminazione delle risorse in caso di uscita dall'ambito (tramite return
un'eccezione o generata).
I lettori attenti delle specifiche avranno notato che il C ++ non ha una clausola finalmente. E questo non è un errore, perché C ++ non ne ha bisogno, poiché RAII gestisce già lo smaltimento delle risorse. (E credetemi, scrivere un distruttore C ++ è molto più facile che scrivere la giusta clausola Java finalmente, o anche il corretto metodo Dispose di C #).
Tuttavia, a volte, una finally
clausola sarebbe interessante. Possiamo farlo in C ++? Sì possiamo! E ancora con un uso alternativo della RAII.
Conclusione: RAII è qualcosa di più che una filosofia in C ++: è C ++
RAII? QUESTO È C ++ !!!
- Commento oltraggiato dello sviluppatore C ++, spudoratamente copiato da un oscuro re di Sparta e dai suoi 300 amici
Quando raggiungi un certo livello di esperienza in C ++, inizi a pensare in termini di RAII , in termini di esecuzione automatizzata di costruttori e distruttori .
Inizi a pensare in termini di ambiti e i caratteri {
e }
diventano tra i più importanti nel tuo codice.
E quasi tutto si adatta perfettamente in termini di RAII: sicurezza delle eccezioni, mutex, connessioni al database, richieste al database, connessione al server, orologi, handle del sistema operativo, ecc. E, ultimo ma non meno importante, memoria.
La parte del database non è trascurabile, in quanto, se accetti di pagare il prezzo, puoi persino scrivere in uno stile di " programmazione transazionale ", eseguendo righe e righe di codice fino a decidere, alla fine, se vuoi fare il commit di tutte le modifiche , o, se non è possibile, ripristinare tutte le modifiche (a condizione che ciascuna riga soddisfi almeno la Garanzia di eccezioni forti). (vedere la seconda parte di questo articolo di Herb's Sutter per la programmazione transazionale).
E come un puzzle, tutto combacia.
RAII è così tanto parte di C ++, C ++ non potrebbe essere C ++ senza di esso.
Questo spiega perché gli sviluppatori C ++ esperti sono così innamorati di RAII e perché RAII è la prima cosa che cercano quando provano un altro linguaggio.
E spiega perché il Garbage Collector, pur essendo un magnifico pezzo di tecnologia in sé, non è così impressionante dal punto di vista di uno sviluppatore C ++:
- RAII gestisce già la maggior parte dei casi gestiti da un GC
- Un GC gestisce meglio di RAII i riferimenti circolari su oggetti gestiti puri (mitigati da usi intelligenti di puntatori deboli)
- Ancora un GC è limitato alla memoria, mentre RAII può gestire qualsiasi tipo di risorsa.
- Come descritto sopra, RAII può fare molto, molto di più ...