Achiziția de resurse este inițializare

Versiunea actuală a paginii nu a fost încă examinată de colaboratori experimentați și poate diferi semnificativ de versiunea revizuită pe 25 aprilie 2019; verificările necesită 10 modificări .

Obținerea unei resurse este inițializare ( ing.  Resource Acquisition Is Initialization (RAII) ) este un limbaj software , al cărui sens constă în faptul că, cu ajutorul anumitor mecanisme software, obținerea unei anumite resurse este indisolubil combinată cu inițializarea și lansarea. - cu distrugerea obiectului.

O modalitate tipică (deși nu singura) de implementare este organizarea accesului la resursă în constructor , iar eliberarea - în destructorul clasei corespunzătoare. În multe limbaje de programare, cum ar fi C++ , destructorul unei variabile este apelat imediat la ieșirea din domeniul său de aplicare atunci când resursa trebuie eliberată. Acest lucru vă permite să garantați eliberarea resursei atunci când apare o excepție : codul devine sigur în caz de excepții ( în engleză  Exception safety ).

În limbile care folosesc un colector de gunoi , un obiect continuă să existe atâta timp cât este referit .

Aplicații

Acest concept poate fi folosit pentru orice obiect sau resursă partajată:

Un caz de utilizare important pentru RAII este „pointerii inteligenți” : clase care încapsulează proprietatea asupra memoriei . De exemplu, există o clasă în biblioteca de șabloane standard C ++ în acest scop (înlocuită cu în C++11 ). auto_ptrunique_ptr

Exemplu

Un exemplu de clasă C++ care implementează capturarea resurselor în timpul inițializării:

#include <cstdio> #include <stdexcept> fisier clasa { public : fișier ( const char * nume fișier ) : m_file_handle ( std :: fopen ( nume fișier , "w+" ) )) { dacă ( ! m_file_handle ) throw std :: runtime_error ( „eșec la deschiderea fișierului” ) ; } ~ fișier () { if ( std :: fclose ( m_file_handle ) != 0 ) { // fclose() poate returna o eroare la scrierea celor mai recente modificări pe disc } } void write ( const char * str ) { if ( std :: fputs ( str , m_file_handle ) == EOF ) throw std :: runtime_error ( "eroare de scriere a fișierului" ) ; } privat : std :: FILE * m_file_handle ; // Copierea și atribuirea nu sunt implementate. Preveniți utilizarea lor prin // declararea metodelor corespunzătoare private. fisier ( fisier const & ) ; fisier & operator = ( fisier const & ) ; }; // un exemplu de utilizare a acestei clase void example_usage () { // deschideți fișierul (luați resursa) fișierul jurnal ( "logfile.txt" ) ; fișier jurnal . scrieți ( "bună ziua jurnal!" ) ; // Continuați să utilizați fișierul jurnal... // Puteți să aruncați excepții sau să ieșiți din funcție fără să vă faceți griji cu privire la închiderea fișierului; // va fi închis automat când variabila fișier jurnal iese din domeniul de aplicare. }

Esența idiomului RAII este că clasa încapsulează proprietatea (capturarea și eliberarea) unei anumite resurse - de exemplu, un descriptor de fișier deschis. Când obiectele de instanță ale unei astfel de clase sunt variabile automate, este garantat că atunci când ies din sfera de aplicare, destructorul lor va fi apelat - ceea ce înseamnă că resursa va fi eliberată. În acest exemplu, fișierul va fi închis corect chiar dacă apelul std::fopen()returnează o eroare și este aruncată o excepție. Mai mult decât atât, dacă constructorul clasei a filefost completat corect, acesta garantează că fișierul este într-adevăr deschis. Dacă apare o eroare în timpul deschiderii fișierului, constructorul aruncă o excepție.

Cu RAII și variabilele automate, proprietatea asupra mai multor resurse poate fi gestionată cu ușurință. Ordinea în care sunt chemați destructorii este inversă ordinii în care sunt chemați constructorii; destructorul este apelat numai dacă obiectul a fost creat complet (adică dacă constructorul nu a aruncat o excepție).

Utilizarea RAII simplifică codul și ajută la asigurarea faptului că programul funcționează corect.

Este posibilă o implementare fără excepții (de exemplu, acest lucru este necesar în aplicațiile încorporate). În acest caz, se folosește constructorul implicit, care resetează gestionarea fișierului și se folosește o metodă separată de tip pentru a deschide fișierul bool FileOpen(const char *). Se păstrează semnificația utilizării unei clase, mai ales dacă există mai multe puncte de ieșire din metoda în care este creat un obiect al clasei. Desigur, în acest caz, necesitatea închiderii fișierului este verificată în destructor.

Gestionarea proprietății resurselor fără RAII

În Java , care utilizează garbage collection , obiectele la care se face referire de variabile automate sunt create atunci când noua comandă este executată și sunt șterse de garbage collector, care rulează automat la intervale nedefinite. Nu există destructori în Java care să fie apelați garantat atunci când o variabilă iese din domeniul de aplicare, iar finalizatoarele disponibile în limbaj pentru eliberarea altor resurse decât memoria nu sunt potrivite, deoarece nu se știe când va fi șters obiectul și dacă va fi șters deloc. Prin urmare, programatorul trebuie să se ocupe el însuși de eliberarea resurselor. Exemplul Java anterior poate fi rescris astfel:

void java_example () { // deschide fișierul (apucă resursa) final LogFile logfile = new LogFile ( "logfile.txt" ) ; încercați { jurnal . scrieți ( "bună ziua jurnal!" ) ; // Continuați să utilizați fișierul jurnal... // Puteți arunca excepții fără să vă faceți griji cu privire la închiderea fișierului. // Fișierul va fi închis când este executat blocul final, care // este garantat să ruleze după blocul try chiar dacă // apar excepții. } în cele din urmă { // eliberează în mod explicit resursa fișierului jurnal . închide (); } }

Aici, sarcina eliberării explicite a resurselor revine programatorului, în fiecare punct al codului în care este preluată o resursă. Java 7 a introdus constructul „try-with-resources” ca zahăr sintactic:

void java_example () { // deschideți fișierul (prindeți resursa) în capul constructului try. // variabila fișier jurnal există doar în acest bloc. încercați ( LogFile logfile = new LogFile ( "logfile.txt" )) { logfile . scrieți ( "bună ziua jurnal!" ) ; // continuă să folosești fișierul de jurnal... } // logfile.close() va fi apelat automat aici, indiferent de // orice excepție din blocul de cod. }

Pentru ca acest cod să funcționeze, clasa LogFile trebuie să implementeze interfața de sistem java.lang.AutoCloseable și să declare un void close();. Această construcție este, de fapt, un analog al using(){}construcției limbajului C#, care realizează și inițializarea unei variabile automate de către un obiect și un apel garantat la metoda de eliberare a resurselor atunci când această variabilă iese din sfera de aplicare.

Ruby și Smalltalk nu acceptă RAII, dar au un model de codare similar în care metodele trec resurse în blocuri de închidere. Iată un exemplu în Ruby:

dosar . deschide ( "logfile.txt" , "w+" ) face | fișier jurnal | fișier jurnal . write ( „bună ziua fișier de jurnal!” ) end # Metoda „deschisă” garantează că fișierul va fi închis fără nicio # acțiune explicită din codul care scrie în fișier

Operatorul ' with' din Python , operatorul ' using' din C# și Visual Basic 2005 oferă control determinist asupra proprietății resurselor dintr-un bloc și înlocuiesc blocul finally, la fel ca în Ruby.

În Perl , durata de viață a obiectelor este determinată folosind contorizarea referințelor , care vă permite să implementați RAII în același mod ca în C++: obiectele care nu există referințe sunt imediat șterse și este apelat destructorul, care poate elibera resursa. Dar, durata de viață a obiectelor nu este neapărat legată de un anumit domeniu. De exemplu, puteți crea un obiect în interiorul unei funcții și apoi alocați o referință la o variabilă globală, crescând astfel durata de viață a obiectului cu o perioadă nedefinită de timp (și lăsând resursa capturată pentru acest timp). Acest lucru poate scurge resurse care ar fi trebuit eliberate atunci când obiectul a ieșit din domeniul de aplicare.

Când scrieți cod în C , este necesar mai mult cod pentru a gestiona proprietatea resurselor, deoarece nu acceptă excepții, blocuri try-finally sau alte constructe de sintaxă care vă permit să implementați RAII. De obicei, codul este scris după următoarea schemă: eliberarea resurselor se realizează la sfârșitul funcției și o etichetă este plasată la începutul acestui cod; în mijlocul funcției, în caz de erori, acestea sunt procesate, iar apoi trecerea la eliberarea resurselor folosind operatorul goto. În C modern, utilizarea goto. În schimb, construcțiile sunt folosite mult mai des if-else. Astfel, codul de eliberare a resursei nu este duplicat în fiecare locație de tratare a erorilor dintr-o singură funcție, dar va trebui să fie duplicat în toate funcțiile în stil sigur pentru fișiere.

int c_example () { int retval = 0 ; // returnează 0 dacă FILE are succes * f = fopen ( "logfile.txt" , "w+" ); dacă ( f ) { do { // Fișierul a fost deschis cu succes, lucrând cu el dacă ( fputs ( "salut fișierul de jurnal!" , f ) == EOF ) { retval = -2 ; rupe ; } // continuă să folosești resursa // ... } while ( 0 ); // Resurse gratuite dacă ( fclose ( f ) == EOF ) { retval = -3 ; } } altfel { // Nu s-a putut deschide fișierul retval = -1 ; } return retval ; }

Există moduri ușor diferite de a scrie un astfel de cod, dar scopul acestui exemplu este de a arăta ideea în general.

Python pseudocod

Puteți exprima ideea RAII în Python astfel:

#coding: utf-8 resource_for_grep = False class RAII : g = globals () def __init__ ( self ): self . g [ 'resource_for_grep' ] = True def __del__ ( self ): self . g [ 'resource_for_grep' ] = Fals print resource_for_grep #False r = RAII () print resource_for_grep #True del r print resource_for_grep #False

Exemplu Perl

Text sursă în Perl #!/usr/bin/perl -w =pentru comentariu Pachetul implementează modelul de proiectare Resource Acquisition Is Initialization (RAII). Obiectul de clasă este creat și inițializat numai atunci când resursa este achiziționată și șters numai când resursa este eliberată. =cut pachetul Resursa { utilizați Scalar::Util qw/refaddr/ ; folosiți strict ; folosiți avertismente ; nostru $self = undef ; # un obiect accesibil din exterior al acestei clase (necesar pentru demonstrație) my %attributes ; # depozit de atribute obiect # -- ** constructor ** -- sub new { my ( $clasa , $resursa ) = ( shift , shift ); my $self = binecuvântează {}, $clasa ; my $id = refaddr $self ; $atribute { $id }{ resursă } = undef ; # inițializați câmpul ascuns al obiectului $self -> set_resource ( $resource ); # setează valoarea câmpului ascuns return $self ; } # -- ** destructor ** -- sub del { my ( $self ) = ( shift ); $self = undef ; } # -- ** inițializare resurse ** -- sub set_resource { my ( $self ) = ( shift ); $self -> { resursa } = shift ; } # -- ** get resource ** -- sub get_resource { my $resource = shift ; # numele (în acest caz și valoarea) resursei fără „refs” stricte ; $self = & { __PACKAGE__ . '::new' }( __PACKAGE__ , $resursa ); # apelați constructorul clasei return $self -> { resource }; # returnează resursă } # -- ** eliberare resursă ** -- sub release_resource { my ( $resource ) = ( shift ); $self = $self -> del () if $self -> { resursa } eq $resursa ; # apelați destructorul pentru o resursă cu o anumită valoare } } pachet principal ; utilizați caracteristica „spune” ; $resursa = Resursa:: get_resource ( 'resursa' ); # apelați resursa și inițializați în același timp, spuneți $resource ; # OUTPUT: resource # valoarea resursei spune $ Resource:: self ; # OUTPUT: Resource=HASH(0x1ce4628) Resource:: release_resource ( 'resursa' ); # eliberați resursa spune $ Resource:: self ; # OUTPUT: Utilizarea valorii neinițializate $Resource::self

Vezi și

Note