Resurssien hankinta on alustamista

Kokeneet kirjoittajat eivät ole vielä tarkistaneet sivun nykyistä versiota, ja se voi poiketa merkittävästi 25. huhtikuuta 2019 tarkistetusta versiosta . tarkastukset vaativat 10 muokkausta .

Resurssin hankkiminen on alustus ( eng.  Resource Acquisition Is Initialization (RAII) ) on ohjelmistokieli , jonka merkitys piilee siinä, että tiettyjen ohjelmistomekanismien avulla tietyn resurssin saaminen yhdistyy erottamattomasti alustukseen ja julkaisuun - esineen tuhoamisen kanssa.

Tyypillinen (joskaan ei ainoa) tapa toteuttaa se on järjestää pääsy resurssiin rakentajassa ja julkaisu - vastaavan luokan destructorissa . Monissa ohjelmointikielissä, kuten C++ , muuttujan destruktoria pyydetään välittömästi poistuttaessa laajuudestaan , kun resurssi on vapautettava. Tämän avulla voit taata resurssin vapauttamisen poikkeuksen sattuessa : koodista tulee turvallinen poikkeustapauksissa ( englanniksi  Exception safety ).

Roskakerääjää käyttävissä kielissä esine on edelleen olemassa niin kauan kuin siihen viitataan .

Sovellukset

Tätä käsitettä voidaan käyttää mille tahansa jaetulle objektille tai resurssille:

Tärkeä RAII:n käyttötapa on "älykkäät osoittimet" : luokat, jotka kapseloivat muistin omistajuuden . Esimerkiksi C ++ -standardimallikirjastossa on luokka tätä tarkoitusta varten (korvattu C ++ 11 : ssä ). auto_ptrunique_ptr

Esimerkki

Esimerkki C++-luokasta, joka toteuttaa resurssien kaappauksen alustuksen aikana:

#include <cstdio> #include <stdexcept> luokkatiedosto { _ julkinen : tiedosto ( const char * tiedostonimi ) : m_file_handle ( std :: fopen ( tiedostonimi , "w+" ) )) { jos ( ! m_tiedoston_kahva ) throw std :: runtime_error ( "tiedoston avaamisvirhe" ) ; } ~ tiedosto () { if ( std :: fclose ( m_file_handle ) != 0 ) { // fclose() saattaa palauttaa virheen kirjoitettaessa uusimpia muutoksia levylle } } void kirjoittaa ( const char * str ) { if ( std :: fputs ( str , m_file_handle ) == EOF ) throw std :: runtime_error ( "tiedoston kirjoitusvirhe" ) ; } yksityinen : std :: TIEDOSTO * m_tiedoston_kahva ; // Kopioi ja kohdistaminen ei ole toteutettu. Estä niiden käyttö // julistamalla vastaavat menetelmät yksityisiksi. tiedosto ( const file & ) ; tiedosto & operaattori = ( const tiedosto & ) ; }; // esimerkki tämän luokan käytöstä void example_usage () { // avaa tiedosto (tarraa resurssi) tiedosto lokitiedosto ( "logfile.txt" ) ; lokitiedosto . write ( "hei lokitiedosto!" ) ; // jatka lokitiedoston käyttöä... // Voit heittää poikkeuksia tai poistua funktiosta huolehtimatta tiedoston sulkemisesta; // se suljetaan automaattisesti, kun lokitiedostomuuttuja menee soveltamisalan ulkopuolelle. }

RAII-idiomin ydin on, että luokka kapseloi jonkin resurssin - esimerkiksi avoimen tiedoston kuvaajan - omistajuuden (kaappaus ja vapauttaminen). Kun tällaisen luokan ilmentymäobjektit ovat automaattisia muuttujia, on taattu, että kun ne menevät soveltamisalan ulkopuolelle, niiden tuhoaja kutsutaan - mikä tarkoittaa, että resurssi vapautuu. Tässä esimerkissä tiedosto suljetaan oikein, vaikka puhelu std::fopen()palauttaisi virheen ja tehdään poikkeus. Lisäksi, jos luokan rakentaja filevalmistui oikein, se takaa, että tiedosto on todellakin auki. Jos tiedostoa avattaessa tapahtuu virhe, rakentaja tekee poikkeuksen.

RAII:n ja automaattisten muuttujien avulla useiden resurssien omistajuutta voidaan hallita helposti. Järjestys, jossa destruktorit kutsutaan, on päinvastainen kuin konstruktoreita kutsutaan; destruktoria kutsutaan vain, jos objekti on luotu kokonaan (eli jos rakentaja ei ole tehnyt poikkeusta).

RAII:n käyttö yksinkertaistaa koodia ja auttaa varmistamaan, että ohjelma toimii oikein.

Toteutus ilman poikkeuksia on mahdollista (esimerkiksi tämä on välttämätöntä sulautetuissa sovelluksissa). Tässä tapauksessa käytetään oletuskonstruktoria, joka nollaa tiedostonkäsittelijän, ja tiedoston avaamiseen käytetään erillistä tyyppitapaa bool FileOpen(const char *). Luokan käytön merkitys säilyy, varsinkin jos menetelmästä, jossa luokan objekti luodaan, on useita poistumispisteitä. Luonnollisesti tässä tapauksessa tiedoston sulkemisen tarve tarkistetaan tuhoajassa.

Resurssien omistajuuden hallinta ilman RAII:ta

Javassa , joka käyttää roskienkeruuta , automaattisten muuttujien viittaamat objektit luodaan, kun uusi komento suoritetaan, ja roskankerääjä poistaa ne, joka suoritetaan automaattisesti määrittelemättömin väliajoin. Javassa ei ole tuhoajia, jotka taatusti kutsuttaisiin, kun muuttuja menee soveltamisalan ulkopuolelle, eivätkä kielellä saatavilla olevat viimeistelyohjelmat muiden resurssien kuin muistin vapauttamiseen, koska ei tiedetä milloin objekti poistetaan ja poistetaanko se. se poistetaan ollenkaan. Siksi ohjelmoijan on itse huolehdittava resurssien vapauttamisesta. Edellinen Java-esimerkki voidaan kirjoittaa uudelleen seuraavasti:

void java_example () { // avaa tiedosto (kaappaa resurssi) final LogFile logfile = new LogFile ( "lokitiedosto.txt" ) ; yritä { lokitiedosto . write ( "hei lokitiedosto!" ) ; // jatka lokitiedoston käyttöä... // Voit heittää poikkeuksia murehtimatta tiedoston sulkemisesta. // Tiedosto suljetaan, kun lopullinen lohko suoritetaan, joka // taatusti suoritetaan try-lohkon jälkeen vaikka // tapahtuisi poikkeuksia. } lopuksi { // vapauta lokitiedostoresurssi erikseen . sulje (); } }

Tässä resurssien eksplisiittisen vapauttamisen taakka on ohjelmoijalla, jokaisessa koodin kohdassa, jossa resurssi otetaan. Java 7 esitteli "try-with-sources" -rakenteen syntaktisena sokerina:

void java_example () { // avaa tiedosto (tarraa resurssi) try-konstruktion päässä. // lokitiedostomuuttuja on vain tässä lohkossa. try ( LogFile lokitiedosto = uusi lokitiedosto ( "logfile.txt" )) { lokitiedosto . write ( "hei lokitiedosto!" ) ; // käytä lokitiedostoa... } // Logfile.close() kutsutaan automaattisesti tähän riippumatta // mahdollisista poikkeuksista koodilohkossa. }

Jotta tämä koodi toimisi, LogFile-luokan on otettava käyttöön java.lang.AutoCloseable-järjestelmäliittymä ja ilmoitettava void close();. Tämä konstruktio on itse asiassa analoginen using(){}C#-kielen rakenteelle, joka myös suorittaa automaattisen muuttujan alustuksen objektin toimesta ja taatun kutsun resurssien vapautusmenetelmälle, kun tämä muuttuja menee soveltamisalan ulkopuolelle.

Ruby ja Smalltalk eivät tue RAII:ta, mutta niillä on samanlainen koodausmalli, jossa menetelmät siirtävät resurssit sulkemislohkoihin. Tässä on esimerkki Rubysta:

tiedosto . open ( "logfile.txt" , "w+" ) do | lokitiedosto | lokitiedosto . write ( "hello lokitiedosto!" ) end # 'Open'-menetelmä takaa, että tiedosto suljetaan ilman mitään nimenomaista # toimintoa tiedostoon kirjoittavasta koodista

withPythonin operaattori ' ' , C# :n ja Visual Basic 2005 :n operaattori ' using' tarjoavat deterministisen hallinnan lohkon resurssien omistajuudelle ja korvaavat lohkon , aivan kuten Rubyssa. finally

Perlissä objektien elinikä määritetään käyttämällä viitelaskentaa , jonka avulla voit toteuttaa RAII:n samalla tavalla kuin C ++:ssa: objektit, joilla ei ole viitteitä, poistetaan välittömästi ja kutsutaan tuhoajaa, joka voi vapauttaa resurssin. Mutta esineiden elinikä ei välttämättä ole sidottu johonkin laajuuteen. Voit esimerkiksi luoda objektin funktion sisään ja liittää siihen sitten viittauksen johonkin globaaliin muuttujaan, mikä pidentää objektin käyttöikää määräämättömällä aikavälillä (ja jättää resurssin kaapatuksi tälle ajalle). Tämä voi vuotaa resursseja, jotka olisi pitänyt vapauttaa, kun objekti meni soveltamisalan ulkopuolelle.

Kun kirjoitat koodia C -kielellä , tarvitaan enemmän koodia resurssien omistajuuden hallintaan, koska se ei tue poikkeuksia, try-finally-lohkoja tai muita syntaksirakenteita, jotka mahdollistavat RAII:n toteuttamisen. Yleensä koodi kirjoitetaan seuraavan kaavan mukaan: resurssien vapauttaminen suoritetaan toiminnon lopussa ja koodin alkuun sijoitetaan etiketti; funktion keskellä, jos virheitä ilmenee, ne käsitellään, ja sitten siirrytään resurssien vapauttamiseen operaattorilla goto. Nykyaikaisessa C:ssä käytetään goto. Sen sijaan rakenteita käytetään paljon useammin if-else. Näin ollen resurssin julkaisukoodia ei kopioida jokaiseen virheenkäsittelypaikkaan yhden funktion sisällä, vaan se on monistettava kaikissa tiedostoturvallisissa funktioissa.

int c_esimerkki () { int retval = 0 ; // palauttaa 0, jos onnistuu TIEDOSTO * f = fopen ( "logfile.txt" , "w+" ); jos ( f ) { tehdä { // Tiedoston avaaminen onnistui, sen kanssa työskentely if ( fputs ( "hello logfile!" , f ) == EOF ) { retval = -2 ; tauko ; } // jatka resurssin käyttöä // ... } while ( 0 ); // Vapaat resurssit if ( fclose ( f ) == EOF ) { retval = -3 ; } } muu { // Tiedoston avaaminen epäonnistui retval = -1 ; } paluu retval ; }

Tällaisen koodin kirjoittamiseen on hieman erilaisia ​​tapoja, mutta tämän esimerkin tarkoituksena on näyttää idea yleisesti.

Python pseudokoodi

Voit ilmaista ajatuksen RAII:sta Pythonissa seuraavasti:

#coding: utf-8 resource_for_grep = Väärä luokka RAII : g = globaalit () def __init__ ( self ): itse . g [ 'resource_for_grep' ] = True def __del__ ( itse ): itse . g [ 'resource_for_grep' ] = Väärin print Resource_for_grep #False r = RAII () print Resource_for_grep #True del r print Resource_for_grep #False

Perl esimerkki

Lähdeteksti Perlissä #!/usr/bin/perl -w =kommenttia varten Paketti toteuttaa Resource Acquisition Is Initialization (RAII) -suunnittelumallin. Luokkaobjekti luodaan ja alustetaan vain, kun resurssi on hankittu, ja poistetaan vain, kun resurssi vapautetaan. = leikata pakettiresurssi { käytä Scalar::Util qw/refaddr/ ; käytä tiukkaa ; käytä varoituksia ; $ itsemme = undef ; # tämän luokan ulkoisesti saatavilla oleva objekti (tarvitaan esittelyyn) my %attributes ; # objektin attribuuttivarasto # -- ** rakentaja ** -- sub new { oma ( $luokka , $resurssi ) = ( siirto , siirto ); minun $self = siunaa {}, $luokka ; minun $id = readdr $self ; $attribuutit { $id }{ resurssi } = undef ; # alusta objektin piilotettu kenttä $self -> set_resource ( $resurssi ); # aseta piilotetun kentän arvo return $self ; } # -- ** destructor ** -- sub del { my ( $self ) = ( shift ); $self = undef ; } # -- ** resurssin alustus ** -- alijoukko_resurssi { oma ( $itse ) = ( shift ) ; $self -> { resurssi } = shift ; } # -- ** hanki resurssi ** -- sub get_resource { my $resurssi = shift ; # resurssin nimi (tässä tapauksessa myös arvo) ei tiukkoja "viitteitä" ; $self = & { __PAKETTI__ . '::uusi' }( __PAKETTI__ , $resurssi ); # kutsua luokan rakentajaa return $self -> { resurssi }; # palautusresurssi } # -- ** julkaisuresurssi ** -- sub release_resurssi { oma ( $resurssi ) = ( shift ); $self = $self -> del () if $self -> { resurssi } eq $resurssi ; # kutsua tietyn arvon omaavan resurssin tuhoajaa } } pääpaketti ; _ käytä ominaisuutta "sano" ; $resurssi = Resurssi:: get_resource ( 'resurssi' ); # kutsu resurssia ja alusta samaan aikaan sano $resurssi ; # OUTPUT: resurssi # resurssin arvo sano $ Resurssi:: itse ; # OUTPUT: Resource=HASH(0x1ce4628) Resurssi:: release_resource ( 'resurssi' ); # vapauta resurssi sano $ Resource:: self ; # OUTPUT: Alustamattoman arvon $Resource::self käyttö

Katso myös

Muistiinpanot