Comprendere il significato del termine e del concetto - RAII (Resource Acquisition is Inizializzazione)

StackOverflow https://stackoverflow.com/questions/712639

Domanda

Potreste voi sviluppatori C++ fornirci una buona descrizione di cos'è RAII, perché è importante e se potrebbe avere o meno rilevanza per altri linguaggi?

IO Fare sapere un po'.Credo che stia per "L'acquisizione di risorse è inizializzazione".Tuttavia, quel nome non concorda con la mia comprensione (forse errata) di cosa sia RAII:Ho l'impressione che RAII sia un modo per inizializzare gli oggetti nello stack in modo tale che, quando tali variabili escono dall'ambito, i distruttori verranno automaticamente chiamati causando la pulizia delle risorse.

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

E come puoi creare qualcosa in pila che causi la pulizia di qualcosa che vive nell'heap?Inoltre, ci sono casi in cui non è possibile utilizzare RAII?Ti capita mai di desiderare la raccolta dei rifiuti?Almeno un garbage collector che potresti usare per alcuni oggetti lasciandone gestire altri?

Grazie.

È stato utile?

Soluzione

  

Quindi, perché non è che ha chiamato "utilizzando lo stack per innescare cleanup" (UTSTTC:)?

RAII ti dice cosa fare: acquisire la risorsa in un costruttore! Vorrei aggiungere: una risorsa, un costruttore. UTSTTC è solo un'applicazione di tale, RAII è molto di più.

Gestione delle risorse fa schifo. Qui, risorsa è tutto ciò che ha bisogno di pulizia dopo l'uso. Studi di progetti attraverso molte piattaforme mostrano la maggior parte dei bug sono legati alla gestione delle risorse - ed è particolarmente male su Windows (a causa dei molti tipi di oggetti e allocatori)

.

In C ++, la gestione delle risorse è particolarmente complessa a causa della combinazione di eccezioni e di modelli di stile (C ++). Per uno sguardo sotto il cofano, consulta GOTW8 ).


C ++ garantisce che il distruttore è chiamato se e solo se il costruttore è riuscito. Basandosi su questo, RAII può risolvere molti problemi fastidiosi il programmatore medio potrebbe anche non essere a conoscenza. Ecco alcuni esempi al di là del "mio variabili locali saranno distrutti ogni volta che torno".

Cominciamo con un troppo semplicistico RAII classe FileHandle impiegando:

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 non riesce (con un'eccezione), nessun altro funzione di membro - nemmeno il distruttore - viene chiamato.

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

Ora, diamo uno sguardo a oggetti temporanei:

void CopyFileData(FileHandle source, FileHandle dest);

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

Ci sono tre casi di errore a manico: nessun file può essere aperto, solo un file può essere aperto, entrambi i file possono essere aperti, ma non è riuscito a copiare i file. In un'implementazione non Raii, Foo avrebbe dovuto gestire tutti e tre i casi esplicitamente.

RAII rilascia le risorse che sono stati acquisiti, anche quando più risorse vengono acquisite all'interno di una dichiarazione.

Ora, cerchiamo di aggregare 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 Logger fallirà se il costruttore di original fallisce (perché filename1 non può essere aperto), il costruttore di duplex fallisce (perché filename2 non può essere aperto), o la scrittura ai file all'interno del corpo del costruttore di Logger fallisce. In ognuno di questi casi, distruttore di Logger sarà non essere chiamato - quindi non possiamo fare affidamento su distruttore di Logger per rilasciare i file. Ma se original è stato costruito, il suo distruttore sarà chiamato durante la pulitura del costruttore Logger.

RAII semplifica pulitura dopo parziale costruzione.


I punti negativi:

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

RAII è a volte ingombrante quando si ha bisogno per acquisizione ritardata, premere oggetti aggregati sul mucchio.
Immaginate il logger ha bisogno di un SetTargetFile(const char* target). In tal caso, la maniglia, che deve ancora essere un membro di Logger, deve risiedere sul mucchio (ad esempio in un puntatore intelligente, per innescare la distruzione del manico in modo appropriato.)

Non ho mai desiderato per la garbage collection davvero. Quando faccio C # a volte mi sento un momento di felicità che ho appena non ho bisogno di cure, ma molto di più mi mancano tutti i giocattoli freddi che possono essere creati attraverso la distruzione deterministica. (Utilizzando IDisposable proprio non è tagliato.)

Ho avuto una struttura particolarmente complessa che potrebbero aver beneficiato di GC, dove "semplici" puntatori intelligenti causerebbe riferimenti circolari su più classi. Abbiamo confuso attraverso bilanciando accuratamente puntatori forza e di debolezza, ma in qualsiasi momento vogliamo cambiare qualcosa, dobbiamo studiare un grafico grande rapporto. GC sarebbe stato meglio, ma alcuni dei componenti risorse che dovrebbero essere sospASAP.


Una nota sul campione FileHandle: Non è stato inteso per essere completa, solo un esempio - ma si è corretto. Grazie Johannes Schaub per sottolineare e FredOverflow per trasformarlo in una soluzione corretta 0x C ++. Nel corso del tempo, ho risolto con i href="http://www.codeproject.com/KB/stl/boostsp_handleref.aspx" approccio documentato qui .

Altri suggerimenti

Non ci sono risposte eccellenti là fuori, quindi ho solo aggiungere alcune cose dimenticate.

0. RAII è sugli ambiti

RAII è di circa due:

  1. l'acquisizione di una risorsa (non importa quale delle risorse) nel costruttore, e un-acquisizione nel distruttore.
  2. avendo il costruttore eseguito quando viene dichiarata la variabile, e il distruttore eseguito automaticamente quando la variabile va fuori del campo di applicazione.

Altri già risposto a tale proposito, in modo da Non mi dilungherò.

1. Quando si codifica in Java o C #, si utilizza già RAII ...

  

MONSIEUR JOURDAIN: Cosa! Quando dico, "Nicole, mi portano le mie pantofole,   e mi danno il mio berretto da notte," questa è la prosa?

     

FILOSOFIA MASTER:. Sì, Signore

     

MONSIEUR JOURDAIN: Per più di quarant'anni ho parlato in prosa senza sapere nulla, e io sono molto grato a voi per avermi insegnato che

.      

- Molière: The Gentleman del ceto medio, Act 2, Scene 4

Come Monsieur Jourdain ha fatto con la prosa, C # e Java anche le persone già utilizzano Raii, ma in modi nascosti. Ad esempio, il seguente codice Java (che è scritto nello stesso modo in C #, sostituendo 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 mutex è fatto nella parola (o synchronized lock), e il non-acquisizione sarà fatto quando si esce dal campo di applicazione

E 'così naturale nella sua notazione non richiede praticamente alcuna spiegazione anche per le persone che non hanno mai sentito parlare di RAII.

Il vantaggio C ++ ha più di Java e C # qui è che qualsiasi cosa può essere fatta usando Raii. Per esempio, non ci sono equivalenti build-in diretto di synchronizedlock in C ++, ma possiamo ancora avere loro.

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 scritta la / C # modo Java (usando le macro C ++):

void foo()
{
   // etc.

   LOCK(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

2. RAII hanno usi alternativi

  

WHITE RABBIT: [cantando] sono in ritardo / Sono in ritardo / Per una data 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)

Si sa quando il costruttore sarà chiamato (alla dichiarazione di oggetto), e non si sa quando il suo distruttore corrispondente sarà chiamato (all'uscita del campo di applicazione), in modo da poter scrivere codice quasi magico con, ma una linea. Benvenuti nel paese delle meraviglie C ++ (almeno, da un punto di vista C ++ dello sviluppatore).

Per esempio, è possibile scrivere un oggetto contatore (che ho lasciato come esercizio) e usarlo solo dichiarando la sua variabile, come l'oggetto di blocco di cui sopra è stato utilizzato:

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 naturalmente, può essere scritto, ancora una volta, il Java / C # modo utilizzando 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é il C ++ manca finally?

  

[GRIDARE] E 'il finale il conto alla rovescia!

     

- Europe: The Final Countdown (mi dispiace, ero fuori di citazioni, qui ...: -)

La clausola finally viene utilizzato in C # / Java per gestire lo smaltimento delle risorse in caso di uscita portata (sia attraverso un return o un eccezione generata).

Astute lettori di specifica avranno notato C ++ non ha alcun clausola finally. E questo non è un errore, perché C ++ non ne ha bisogno, come già RAII gestire lo smaltimento delle risorse. (E mi creda, la scrittura di un C ++ distruttore è magnitudini più facile che scrivere il diritto Java clausola finally, o anche un C # 's corretto metodo Dispose).

Ancora, a volte, una clausola finally sarebbe cool. Possiamo farlo in C ++? Yes, we can! E di nuovo con un uso alternativo della RAII.

Conclusione: RAII è una filosofia più che in C ++: E 'C ++

  

Raii? QUESTO E 'C ++ !!!

     

- C ++ di indignato sviluppatorecommentare, spudoratamente copiato da uno Sparta re oscuro e dei suoi 300 amici

Quando si raggiunge un certo livello di esperienza in C ++, si inizia a pensare in termini di RAII , in termini di esecuzione construtors e distruttori automatizzati .

Si inizia a pensare in termini di oscilloscopi , ei personaggi { e } diventano quelle del più importante nel codice.

E quasi tutto si adatta a destra in termini di RAII: sicurezza rispetto alle eccezioni, mutex, le connessioni al database, richieste al database, la connessione al server, orologi, maniglie del sistema operativo, ecc, e ultimo, ma non meno importante, la memoria

.

La parte del database non è trascurabile, come, se si accetta di pagare il prezzo, si può anche scrivere in un " programmazione transazionale " stile, l'esecuzione di linee e righe di codice fino a decidere, nel fine, se si vuole impegnare tutte le modifiche, o, se non possibile, avendo tutti i cambiamenti ritornato di nuovo (a patto che ogni linea di soddisfare almeno Garanzia Exception Strong). (Vedere la seconda parte di questo di Herb Sutter articolo per la programmazione transazionale ).

E come un puzzle, tutto si adatta.

RAII è tanto parte di C ++, C ++ non potrebbe essere C ++ senza di essa.

Questo spiega il motivo per cui esperti sviluppatori C ++ sono così innamorato con Raii, e perché RAII è la prima cosa si cerca quando si cerca un'altra lingua.

E spiega perché il Garbage Collector, mentre un pezzo magnifico della tecnologia in sé, non è così impressionante dal punto di vista di un C ++ dello sviluppatore:

  • RAII gestisce già la maggior parte dei casi trattati da un GC
  • di offerte di GC meglio di RAII con i riferimenti circolari su oggetti gestiti puri (mitigata da usi intelligenti di puntatori deboli)
  • Ancora una GC è limitata a memoria, mentre RAII grado di gestire qualsiasi tipo di risorsa.
  • Come descritto in precedenza, RAII può fare molto, molto di più ...

RAII utilizza la semantica dei distruttori C++ per gestire le risorse.Consideriamo ad esempio un puntatore intelligente.Hai un costruttore parametrizzato del puntatore che inizializza questo puntatore con l'indirizzo dell'oggetto.Assegni 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 la RAII non aiuta.Ad esempio, se utilizzi puntatori intelligenti con 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 in questo senso.

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

Si chiama Resource acquisizione è di inizializzazione in quanto la risorsa è acquisito quando si costruisce l'oggetto che controlla la risorsa, se il costruttore non è riuscita (per esempio a causa di un'eccezione) la risorsa non è acquisito. Poi, una volta che l'oggetto va fuori del campo di applicazione è rilasciata la risorsa. C ++ garantisce che tutti gli oggetti nello stack che sono state costruite con successo sarà distrutto (questo include costruttori di classi base e membri anche se il costruttore di super-classe non riesce).

Il razionale dietro RAII è quello di fare un'eccezione acquisizione delle risorse di sicurezza. Che tutte le risorse acquisite siano correttamente rilasciati non importa dove si verifica un'eccezione. Tuttavia questo affidamento sulla qualità della classe che acquisisce la risorsa (questa deve essere un'eccezione al sicuro e questo è difficile).

mi piacerebbe mettere un po 'più forte, allora le risposte precedenti.

Raii, Resource acquisizione è di inizializzazione significa che tutte le risorse acquisite dovrebbero essere acquisite nel contesto del l'inizializzazione di un oggetto. Questo impedisce l'acquisizione di risorse "nudo". La logica è che pulizia in C ++ funziona sulla base oggetto, non funziona-chiamata base. Quindi, tutte le pulizie dovrebbe essere fatto da oggetti, non chiamate di funzione. In questo senso C ++ è più oggetto orientato quindi ad esempio Giava. pulizia Java si basa su chiamate di funzione di clausole finally.

Il problema con la raccolta dei rifiuti è che si perde la distruzione deterministica che è fondamentale per Raii. Una volta che una variabile passa nell'ambito, spetta al collettore garbage quando verrà recuperato l'oggetto. La risorsa che è detenuto da l'oggetto continuerà ad essere tenuto fino il distruttore viene chiamato.

RAII viene dalla allocazione delle risorse sia di inizializzazione. Fondamentalmente, ciò significa che quando un costruttore termina l'esecuzione, l'oggetto costruito è completamente inizializzato e pronto all'uso. Essa implica anche che il distruttore rilascerà le risorse di memoria (ad esempio, le risorse del sistema operativo) di proprietà dell'oggetto.

Rispetto ai rifiuti raccolti lingue / tecnologie (ad esempio Java, .NET), C ++ permette il pieno controllo della vita di un oggetto. Per un oggetto pila allocato, saprete quando sarà chiamato il distruttore dell'oggetto (quando l'esecuzione va fuori del campo di applicazione), cosa che non è in realtà controllata in caso di garbage collection. Anche usando puntatori intelligenti in C ++ (per esempio boost :: shared_ptr), saprete che, quando non v'è alcun riferimento alla oggetto appuntito, si chiamerà il distruttore di tale oggetto.

  

E come si può fare qualcosa sullo stack che causerà la pulitura 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 all'esistenza deve avere una dimensione e allocherà la memoria necessaria. Quando si va fuori del campo di applicazione, è distruttore viene chiamato. Questo è molto utile per cose come oggetti di sincronizzazione. Considerare

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 capita mai di trovare se stessi che desiderano per la raccolta dei rifiuti? Almeno un garbage collector è possibile utilizzare per alcuni oggetti, mentre lasciare che gli altri essere gestito?

Mai. garbage collection risolve solo un piccolo sottoinsieme di gestione dinamica delle risorse.

Ci sono già un sacco di buone risposte qui, ma vorrei solo aggiungere:
Una semplice spiegazione di Raii è che, in C ++, un oggetto allocato sullo stack viene distrutta ogni volta che passa nell'ambito. Ciò significa che, un distruttore oggetti sarà chiamato e può fare tutto la pulizia necessaria.
Ciò significa che, se un oggetto viene creato senza "nuovo", non "cancella" è richiesto. E questo è anche l'idea alla base di "puntatori intelligenti" -. Risiedono sullo stack, ed essenzialmente avvolge un oggetto basato mucchio

RAII è l'acronimo di risorse acquisizione è di inizializzazione.

Questa tecnica è molto unica per C ++ a causa del loro supporto sia per costruttori e distruttori e quasi automaticamente i costruttori che sono corrispondenti che gli argomenti vengono passati o peggiore dei casi il costruttore di default è chiamato & distruttori se esplicitamente previsto è chiamato altrimenti quello di default che viene aggiunto dal ++ compilatore C è chiamata se non si scrive un distruttore esplicitamente per una classe C ++. Questo accade solo per gli oggetti C ++ che sono auto-gestito - significato che non utilizzano il negozio libero (memoria allocata / deallocata usando nuovo, nuovo [] / delete, delete [] operatori C ++).

tecnica RAII fa uso di questa funzione oggetto di auto-gestita per gestire gli oggetti che vengono creati sul mucchio / libero-store by explcitly chiedendo più memoria utilizzando il nuovo / nuovo [], che dovrebbe essere esplicitamente distrutto chiamando delete / Elimina[]. classe dell'oggetto auto-gestito avvolgerà questo un altro oggetto che viene creato nella memoria heap / libero-store. Pertanto, quando viene eseguito il costruttore dell'oggetto auto gestiti, l'oggetto avvolto viene creato nella memoria heap / privo di deposito e quando la maniglia dell'oggetto automatico gestiti passa nell'ambito, distruttore di quell'oggetto auto-gestita viene chiamato automaticamente in cui il avvolto oggetto viene distrutto utilizzando eliminare. Con concetti OOP, se si avvolge questi oggetti all'interno di un'altra classe in ambito privato, si avrebbe non hanno accesso alle classi avvolti membri e metodi e questo è il motivo per cui i puntatori intelligenti (aka gestire classi) sono stati progettati per. Questi puntatori intelligenti espongono l'oggetto avvolto come oggetto tipizzato al mondo esterno e non consentendo di richiamare qualsiasi membro / metodi che l'oggetto di memoria esposto è costituito da. Si noti che puntatori intelligenti hanno vari gusti in base alle diverse esigenze. Si dovrebbe fare riferimento alla programmazione moderna C ++ da Andrei Alexandrescu o aumentare della biblioteca (www.boostorg) implementazione shared_ptr.hpp / documentazione per saperne di più su di esso. Spero che questo ti aiuta a capire RAII.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top