Comprensione del significato del termine e del concetto - RAII (Resource Acquisition is Initialization)


110

Potreste sviluppatori C ++ per favore darci una buona descrizione di cosa sia RAII, perché è importante e se potrebbe avere rilevanza o meno per altri linguaggi?

Io faccio conoscere un po '. Credo che stia per "Resource Acquisition is Initialization". Tuttavia, quel nome non coincide con la mia (forse errata) comprensione di cosa sia RAII: ho l'impressione che RAII sia un modo per inizializzare gli oggetti sullo stack in modo tale che, quando quelle variabili escono dall'ambito, i distruttori essere chiamato causando la pulizia delle risorse.

Allora perché non si chiama "usare lo stack per attivare la pulizia" (UTSTTC :)? Come si arriva da lì a "RAII"?

E come puoi creare qualcosa in pila che causerà la pulizia di qualcosa che vive sul mucchio? Inoltre, ci sono casi in cui non è possibile utilizzare RAII? Ti sei mai trovato a desiderare la raccolta dei rifiuti? Almeno un garbage collector che potresti usare per alcuni oggetti mentre ne lasci gestire altri?

Grazie.


27
UTSTTC? Mi piace! È molto più intuitivo di RAII. RAII è male chiamato, dubito che qualsiasi programmatore C ++ potrebbero contestare questo. Ma non è facile cambiare. ;)
jalf

10
Ecco il punto di vista di Stroustrup sulla questione: groups.google.com/group/comp.lang.c++.moderated/msg/…
sbi

3
@sbi: Comunque, +1 sul tuo commento solo per la ricerca storica. Credo che avere il punto di vista dell'autore (B. Stroustrup) sul nome di un concetto (RAII) sia abbastanza interessante da avere una sua risposta.
paercebal

1
@paercebal: ricerca storica? Adesso mi hai fatto sentire molto vecchio. :(Allora stavo leggendo l'intero thread e non mi consideravo nemmeno un principiante di C ++!
sbi

3
+1, stavo per fare la stessa domanda, sono contento di non essere l'unico a capire il concetto ma non ha senso il nome. Sembra che avrebbe dovuto chiamarsi RAOI - Resource Acquisition On Initialization.
laurent

Risposte:


132

Allora perché non si chiama "usare lo stack per attivare la pulizia" (UTSTTC :)?

RAII ti sta dicendo cosa fare: acquisire la tua risorsa in un costruttore! Aggiungerei: una risorsa, un costruttore. UTSTTC è solo una delle sue applicazioni, RAII è molto di più.

La gestione delle risorse fa schifo. Qui, la risorsa è tutto ciò che deve essere pulito dopo l'uso. Studi di progetti su molte piattaforme mostrano che la maggior parte dei bug è correlata alla gestione delle risorse, ed è particolarmente dannosa su Windows (a causa dei molti tipi di oggetti e allocatori).

In C ++, la gestione delle risorse è particolarmente complicata a causa della combinazione di eccezioni e modelli (in stile C ++). Per una sbirciatina sotto il cofano, vedi GOTW8 ).


C ++ garantisce che il distruttore venga chiamato se e solo se il costruttore ha avuto successo. Basandosi su questo, RAII può risolvere molti problemi fastidiosi di cui il programmatore medio potrebbe non essere nemmeno a conoscenza. Ecco alcuni esempi oltre a "le mie variabili locali verranno distrutte ogni volta che torno".

Cominciamo con una FileHandleclasse eccessivamente semplicistica che impiega RAII:

class FileHandle
{
    FILE* file;

public:

    explicit FileHandle(const char* name)
    {
        file = fopen(name);
        if (!file)
        {
            throw "MAYDAY! MAYDAY";
        }
    }

    ~FileHandle()
    {
        // The only reason we are checking the file pointer for validity
        // is because it might have been moved (see below).
        // It is NOT needed to check against a failed constructor,
        // because the destructor is NEVER executed when the constructor fails!
        if (file)
        {
            fclose(file);
        }
    }

    // The following technicalities can be skipped on the first read.
    // They are not crucial to understanding the basic idea of RAII.
    // However, if you plan to implement your own RAII classes,
    // it is absolutely essential that you read on :)



    // It does not make sense to copy a file handle,
    // hence we disallow the otherwise implicitly generated copy operations.

    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;



    // The following operations enable transfer of ownership
    // and require compiler support for rvalue references, a C++0x feature.
    // Essentially, a resource is "moved" from one object to another.

    FileHandle(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
    }

    FileHandle& operator=(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
        return *this;
    }
}

Se la costruzione fallisce (con un'eccezione), non viene chiamata nessun'altra funzione membro, nemmeno il distruttore.

RAII evita di utilizzare oggetti in uno stato non valido. rende la vita già più facile prima ancora di usare l'oggetto.

Ora, diamo uno sguardo agli oggetti temporanei:

void CopyFileData(FileHandle source, FileHandle dest);

void Foo()
{
    CopyFileData(FileHandle("C:\\source"), FileHandle("C:\\dest"));
}

Ci sono tre casi di errore da gestire: nessun file può essere aperto, solo un file può essere aperto, entrambi i file possono essere aperti ma la copia dei file non è riuscita. In un'implementazione non RAII, Foodovrebbe gestire tutti e tre i casi in modo esplicito.

RAII rilascia le risorse che sono state acquisite, anche quando vengono acquisite più risorse all'interno di un'istruzione.

Ora, aggreghiamo alcuni oggetti:

class Logger
{
    FileHandle original, duplex;   // this logger can write to two files at once!

public:

    Logger(const char* filename1, const char* filename2)
    : original(filename1), duplex(filename2)
    {
        if (!filewrite_duplex(original, duplex, "New Session"))
            throw "Ugh damn!";
    }
}

Il costruttore di Loggerfallirà se originalil costruttore di s fallisce (perché filename1non può essere aperto), duplexil costruttore di fallisce (perché filename2non può essere aperto), o la scrittura sui file all'interno Loggerdel corpo del costruttore fallisce. In nessuno di questi casi, Loggeril distruttore di non verrà chiamato, quindi non possiamo fare affidamento sul Loggerdistruttore di per rilasciare i file. Ma se è originalstato costruito, il suo distruttore verrà chiamato durante la pulizia del Loggercostruttore.

RAII semplifica la pulizia dopo la costruzione parziale.


Punti negativi:

Punti negativi? Tutti i problemi possono essere risolti con RAII e puntatori intelligenti ;-)

RAII a volte è ingombrante quando è necessaria un'acquisizione ritardata, spingendo gli oggetti aggregati nell'heap.
Immagina che il logger abbia bisogno di un file SetTargetFile(const char* target). In tal caso, l'handle, che deve ancora essere un membro Logger, deve risiedere sull'heap (ad esempio in un puntatore intelligente, per attivare la distruzione dell'handle in modo appropriato).

Non ho mai desiderato veramente la raccolta dei rifiuti. Quando faccio C # a volte provo un momento di beatitudine di cui non ho bisogno di preoccuparmi, ma molto di più mi mancano tutti i fantastici giocattoli che possono essere creati attraverso la distruzione deterministica. (usare IDisposablesolo non lo taglia.)

Ho avuto una struttura particolarmente complessa che avrebbe potuto beneficiare di GC, in cui i puntatori intelligenti "semplici" causerebbero riferimenti circolari su più classi. Ci siamo arrangiati bilanciando attentamente gli indicatori forti e deboli, ma ogni volta che vogliamo cambiare qualcosa, dobbiamo studiare un grande diagramma di relazione. GC avrebbe potuto essere migliore, ma alcuni dei componenti contenevano risorse che dovrebbero essere rilasciate al più presto.


Una nota sull'esempio FileHandle: non doveva essere completo, solo un esempio, ma si è rivelato errato. Grazie Johannes Schaub per averlo indicato e FredOverflow per averlo trasformato in una soluzione C ++ 0x corretta. Nel tempo, mi sono accordato con l'approccio qui documentato .


1
+1 Per indicare che GC e ASAP non si accoppiano. Non fa male spesso ma quando fa non è facile diagnosticare: /
Matthieu M.

10
Una frase in particolare che ho trascurato nelle letture precedenti. Hai detto che "RAII" ti sta dicendo: "Acquisisci le tue risorse all'interno dei costruttori". Ha senso ed è quasi una parafrasi parola per parola di "RAII". Ora capisco ancora meglio (ti voterei di nuovo se potessi :)
Charlie Flowers il

2
Uno dei principali vantaggi di GC è che un framework di allocazione della memoria può impedire la creazione di riferimenti penzolanti in assenza di codice "non sicuro" (se il codice "non sicuro" è consentito, ovviamente, il framework non può impedire nulla). GC è spesso superiore a RAII quando si tratta di oggetti immutabili condivisi come stringhe che spesso non hanno un proprietario chiaro e non richiedono pulizia. È un peccato che più framework non cerchino di combinare GC e RAII, poiché la maggior parte delle applicazioni avrà un mix di oggetti immutabili (dove GC sarebbe il migliore) e oggetti che necessitano di pulizia (dove RAII è il migliore).
supercat

@supercat: generalmente mi piace GC - ma funziona solo per le risorse che GC "capisce". Ad esempio, .NET GC non conosce il costo degli oggetti COM. Quando si creano e si distruggono semplicemente in un ciclo, l'applicazione lascerà felicemente correre nel terreno per quanto riguarda lo spazio degli indirizzi o la memoria virtuale - qualunque cosa venga prima - senza nemmeno pensare di fare un GC. --- inoltre, anche in un ambiente perfettamente GC, mi manca ancora il potere della distruzione deterministica: puoi applicare lo stesso pattern ad altri artificiali, ad esempio mostrando gli elementi dell'interfaccia utente in condizioni certian.
Peterchen

@ Peterchen: Una cosa che penso sia assente in molti pensieri relativi all'OOP è il concetto di proprietà dell'oggetto. Tenere traccia della proprietà è spesso chiaramente necessario per gli oggetti con risorse, ma spesso è anche necessario per gli oggetti mutabili senza risorse. In generale, gli oggetti dovrebbero incapsulare il loro stato mutabile o in riferimenti a oggetti immutabili eventualmente condivisi, o in oggetti mutabili di cui sono il proprietario esclusivo. Tale proprietà esclusiva non implica necessariamente l'accesso in scrittura esclusivo, ma se lo Foopossiede Bare Bozlo modifica, ...
supercat

42

Ci sono ottime risposte là fuori, quindi aggiungo solo alcune cose dimenticate.

0. RAII riguarda gli scopi

RAII riguarda entrambi:

  1. acquisire una risorsa (non importa quale risorsa) nel costruttore e annullarne l'acquisizione nel distruttore.
  2. 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 synchronizedcon 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 ( synchronizedo 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 synchronizedlockin 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 finallyclausola viene utilizzata in C # / Java per gestire l'eliminazione delle risorse in caso di uscita dall'ambito (tramite returnun'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 finallyclausola 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ù ...

Un fan di Java: direi che GC è molto più utile di RAII in quanto gestisce tutta la memoria e ti libera da molti potenziali bug. Con GC, puoi creare riferimenti circolari, restituire e memorizzare riferimenti ed è difficile sbagliare (archiviare un riferimento a un oggetto apparentemente di breve durata ne allunga il tempo di vita, che è una sorta di perdita di memoria, ma questo è l'unico problema) . La gestione delle risorse con GC non funziona, ma la maggior parte delle risorse in un'applicazione ha un ciclo di vita banale e le poche rimanenti non sono un grosso problema. Vorrei che potessimo avere sia GC che RAII, ma sembra impossibile.
maaartinus

16

1
Alcuni di questi sono in linea con la mia domanda, ma una ricerca non li ha trovati, né l'elenco delle "domande correlate" che appare dopo aver inserito una nuova domanda. Grazie per i link.
Charlie Flowers

1
@Charlie: la build nella ricerca è molto debole in qualche modo. L'uso della sintassi del tag ("[topic]") è molto utile e molte persone usano google ...
dmckee --- ex-moderator kitten

10

RAII utilizza la semantica dei distruttori C ++ per gestire le risorse. Ad esempio, considera un puntatore intelligente. Hai un costruttore parametrizzato del puntatore che inizializza questo puntatore con l'indirizzo dell'oggetto. Si alloca un puntatore sullo stack:

SmartPointer pointer( new ObjectClass() );

Quando il puntatore intelligente esce dall'ambito, il distruttore della classe del puntatore elimina l'oggetto connesso. Il puntatore è allocato nello stack e l'oggetto allocato nell'heap.

Ci sono alcuni casi in cui RAII non aiuta. Ad esempio, se utilizzi puntatori intelligenti per il conteggio dei riferimenti (come boost :: shared_ptr) e crei una struttura simile a un grafico con un ciclo, rischi di affrontare una perdita di memoria perché gli oggetti in un ciclo impediranno il rilascio reciproco. La raccolta dei rifiuti aiuterebbe contro questo.


2
Quindi dovrebbe chiamarsi UCDSTMR :)
Daniel Daranas

Ripensandoci, penso che UDSTMR sia più appropriato. Viene fornito il linguaggio (C ++), quindi la lettera "C" non è necessaria nell'acronimo. UDSTMR sta per Using Destructor Semantics To Manage Resources.
Daniel Daranas

9

Vorrei metterlo un po 'più forte rispetto alle risposte precedenti.

RAII, Resource Acquisition Is Initialization significa che tutte le risorse acquisite devono essere acquisite nel contesto dell'inizializzazione di un oggetto. Ciò vieta l'acquisizione di risorse "nuda". La logica è che la pulizia in C ++ funziona sulla base degli oggetti, non sulla base delle chiamate di funzione. Quindi, tutta la pulizia dovrebbe essere eseguita da oggetti, non da chiamate di funzioni. In questo senso il C ++ è più orientato agli oggetti rispetto ad es. Java. La pulizia di Java si basa sulle chiamate di funzione nelle finallyclausole.


Bella risposta. E "inizializzazione di un oggetto" significa "costruttori", vero?
Charlie Flowers,

@Charlie: sì, soprattutto in questo caso.
MSalters

8

Sono d'accordo con cpitis. Ma vorrei aggiungere che le risorse possono essere qualsiasi cosa non solo la memoria. La risorsa potrebbe essere un file, una sezione critica, un thread o una connessione al database.

Si chiama Resource Acquisition Is Initialization perché la risorsa viene acquisita quando viene costruito l'oggetto che controlla la risorsa. Se il costruttore fallisce (cioè a causa di un'eccezione) la risorsa non viene acquisita. Quindi, una volta che l'oggetto esce dall'ambito, la risorsa viene rilasciata. c ++ garantisce che tutti gli oggetti sullo stack che sono stati costruiti con successo verranno distrutti (questo include i costruttori delle classi base e dei membri anche se il costruttore della super classe fallisce).

La logica alla base della RAII è rendere sicura l'eccezione dell'acquisizione di risorse. Che tutte le risorse acquisite vengano rilasciate correttamente indipendentemente da dove si verifica un'eccezione. Tuttavia, questo dipende dalla qualità della classe che acquisisce la risorsa (questo deve essere sicuro rispetto alle eccezioni e questo è difficile).


Eccellente, grazie per aver spiegato la logica dietro il nome. A quanto ho capito, potresti parafrasare RAII come, "Non acquisire mai alcuna risorsa attraverso un meccanismo diverso dall'inizializzazione (basata sul costruttore)". Sì?
Charlie Flowers

Sì, questa è la mia politica, tuttavia sono molto diffidente nello scrivere i miei corsi RAII poiché devono essere sicuri rispetto alle eccezioni. Quando li scrivo cerco di garantire la sicurezza delle eccezioni riutilizzando altre classi RAII scritte da esperti.
Iain

Non li ho trovati difficili da scrivere. Se le tue classi sono sufficientemente piccole, non sono affatto difficili.
Rob K

7

Il problema con la raccolta dei rifiuti è che si perde la distruzione deterministica che è cruciale per RAII. Una volta che una variabile esce dall'ambito, spetta al garbage collector quando l'oggetto verrà recuperato. La risorsa contenuta nell'oggetto continuerà a essere conservata fino a quando non viene chiamato il distruttore.


4
Il problema non è solo il determinismo. Il vero problema è che i finalizzatori (denominazione java) si frappongono a GC. GC è efficiente perché non richiama gli oggetti morti, ma piuttosto li ignora nell'oblio. I GC devono tenere traccia degli oggetti con finalizzatori in un modo diverso per garantire che siano chiamati
David Rodríguez - dribeas

1
tranne che in java / c # dovresti probabilmente ripulire in un blocco finalmente piuttosto che in un finalizzatore.
jk.

4

RAII proviene da Resource Allocation Is Initialization. Fondamentalmente, significa che quando un costruttore termina l'esecuzione, l'oggetto costruito è completamente inizializzato e pronto per l'uso. Implica anche che il distruttore rilascerà tutte le risorse (ad esempio memoria, risorse del sistema operativo) possedute dall'oggetto.

Rispetto ai linguaggi / tecnologie raccolti dalla spazzatura (ad esempio Java, .NET), il C ++ consente il pieno controllo della vita di un oggetto. Per un oggetto allocato nello stack, saprai quando verrà chiamato il distruttore dell'oggetto (quando l'esecuzione esce dallo scope), cosa che non è realmente controllata in caso di garbage collection. Anche usando i puntatori intelligenti in C ++ (ad esempio boost :: shared_ptr), saprai che quando non c'è riferimento all'oggetto puntato, verrà chiamato il distruttore di quell'oggetto.


3

E come puoi creare qualcosa in pila che causerà la pulizia di qualcosa che vive sul mucchio?

class int_buffer
{
   size_t m_size;
   int *  m_buf;

   public:
   int_buffer( size_t size )
     : m_size( size ), m_buf( 0 )
   {
       if( m_size > 0 )
           m_buf = new int[m_size]; // will throw on failure by default
   }
   ~int_buffer()
   {
       delete[] m_buf;
   }
   /* ...rest of class implementation...*/

};


void foo() 
{
    int_buffer ib(20); // creates a buffer of 20 bytes
    std::cout << ib.size() << std::endl;
} // here the destructor is called automatically even if an exception is thrown and the memory ib held is freed.

Quando un'istanza di int_buffer viene creata, deve avere una dimensione e allocherà la memoria necessaria. Quando esce dall'ambito, viene chiamato il suo distruttore. Questo è molto utile per cose come gli oggetti di sincronizzazione. Tener conto di

class mutex
{
   // ...
   take();
   release();

   class mutex::sentry
   {
      mutex & mm;
      public:
      sentry( mutex & m ) : mm(m) 
      {
          mm.take();
      }
      ~sentry()
      {
          mm.release();
      }
   }; // mutex::sentry;
};
mutex m;

int getSomeValue()
{
    mutex::sentry ms( m ); // blocks here until the mutex is taken
    return 0;  
} // the mutex is released in the destructor call here.

Inoltre, ci sono casi in cui non è possibile utilizzare RAII?

No, non proprio.

Ti sei mai trovato a desiderare la raccolta dei rifiuti? Almeno un garbage collector che potresti usare per alcuni oggetti mentre ne lasci gestire altri?

Mai. La raccolta dei rifiuti risolve solo un piccolo sottoinsieme della gestione dinamica delle risorse.


Ho usato Java e C # molto poco, quindi non ho mai dovuto perderlo, ma GC ha certamente ristretto il mio stile quando si trattava di gestione delle risorse quando dovevo usarli, perché non potevo usare RAII.
Rob K

1
Ho usato molto C # e sono d'accordo con te al 100%. In effetti, considero un GC non deterministico una responsabilità in una lingua.
Nemanja Trifunovic

2

Ci sono già molte buone risposte qui, ma vorrei solo aggiungere:
una semplice spiegazione di RAII è che, in C ++, un oggetto allocato nello stack viene distrutto ogni volta che esce dall'ambito. Ciò significa che verrà chiamato un distruttore di oggetti che può eseguire tutte le operazioni di pulizia necessarie.
Ciò significa che se un oggetto viene creato senza "nuovo", non è richiesta alcuna "eliminazione". E questa è anche l'idea alla base dei "puntatori intelligenti": risiedono nello stack e essenzialmente avvolgono un oggetto basato sull'heap.


1
No, non lo fanno. Ma hai una buona ragione per creare un puntatore intelligente sull'heap? A proposito, il puntatore intelligente era solo un esempio di come RAII può essere utile.
E Dominique

1
Forse il mio uso di "stack" rispetto a "heap" è un po 'sciatto - per un oggetto sullo "stack" intendevo qualsiasi oggetto locale. Può naturalmente essere una parte di un oggetto, ad esempio sul mucchio. Con "creare un puntatore intelligente sull'heap", intendevo usare new / delete sul puntatore intelligente stesso.
E Dominique

1

RAII è l'acronimo di Resource Acquisition Is Initialization.

Questa tecnicaèmolto unica in C ++ a causa del loro supporto sia per i costruttori che per i distruttori e quasi automaticamente i costruttori che corrispondono agli argomenti passati o nel caso peggiore il costruttore predefinito è chiamato & distruttori se esplicitamente fornito è chiamato altrimenti quello predefinito che viene aggiunto dal compilatore C ++ viene chiamato se non hai scritto un distruttore esplicitamente per una classe C ++. Ciò accade solo per gli oggetti C ++ che sono gestiti automaticamente, ovvero che non utilizzano l'archivio libero (memoria allocata / deallocata utilizzando operatori C ++ new, new [] / delete, delete []).

La tecnica RAII fa uso di questa funzionalità degli oggetti gestiti automaticamente per gestire gli oggetti che vengono creati nell'heap / free-store chiedendo esplicitamente più memoria usando new / new [], che dovrebbe essere esplicitamente distrutto chiamando delete / delete [] . La classe dell'oggetto gestito automaticamente avvolgerà questo altro oggetto creato nella memoria heap / free-store. Quindi, quando viene eseguito il costruttore dell'oggetto gestito automaticamente, l'oggetto avvolto viene creato nella memoria heap / free-store e quando l'handle dell'oggetto gestito automaticamente esce dall'ambito, il distruttore di quell'oggetto gestito automaticamente viene chiamato automaticamente in cui viene oggetto viene distrutto utilizzando delete. Con i concetti OOP, se avvolgi tali oggetti all'interno di un'altra classe in ambito privato, non avresti accesso ai membri e ai metodi delle classi incapsulate e questo è il motivo per cui sono progettati i puntatori intelligenti (ovvero le classi handle). Questi puntatori intelligenti espongono l'oggetto avvolto come oggetto digitato al mondo esterno e lì, consentendo di invocare qualsiasi membro / metodo di cui è composto l'oggetto di memoria esposto. Nota che i puntatori intelligenti hanno vari gusti in base alle diverse esigenze. Dovresti fare riferimento alla programmazione Modern C ++ di Andrei Alexandrescu o all'implementazione / documentazione della libreria boost (www.boostorg) shared_ptr.hpp per saperne di più. Spero che questo ti aiuti a capire RAII. Dovresti fare riferimento alla programmazione Modern C ++ di Andrei Alexandrescu o all'implementazione / documentazione della libreria boost (www.boostorg) shared_ptr.hpp per saperne di più. Spero che questo ti aiuti a capire RAII. Dovresti fare riferimento alla programmazione Modern C ++ di Andrei Alexandrescu o all'implementazione / documentazione della libreria boost (www.boostorg) shared_ptr.hpp per saperne di più. Spero che questo ti aiuti a capire RAII.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.