Resursförvärv är initiering

Den aktuella versionen av sidan har ännu inte granskats av erfarna bidragsgivare och kan skilja sig väsentligt från versionen som granskades den 25 april 2019; kontroller kräver 10 redigeringar .

Att erhålla en resurs är initiering ( eng.  Resource Acquisition Is Initialization (RAII) ) är ett mjukvaruformspråk , vars innebörd ligger i det faktum att med hjälp av vissa mjukvarumekanismer är erhållandet av en viss resurs oupplösligt kombinerat med initiering och frigivning - med förstörelsen av föremålet.

Ett typiskt (även om det inte är det enda) sättet att implementera det är att organisera åtkomsten till resursen i konstruktorn och utgåvan - i destruktorn för motsvarande klass. I många programmeringsspråk, som C++ , anropas en variabels destruktor omedelbart när den lämnas ur dess räckvidd när resursen behöver frigöras. Detta gör att du kan garantera frigörandet av resursen när ett undantag inträffar : koden blir säker vid undantag ( engelska  Exception safety ).

På språk som använder en sophämtare fortsätter ett objekt att existera så länge det refereras till det .

Applikationer

Det här konceptet kan användas för alla delade objekt eller resurs:

Ett viktigt användningsfall för RAII är "smarta pekare" : klasser som kapslar in äganderätten till minnet . Till exempel finns det en klass i C ++ standardmallbiblioteket för detta ändamål auto_ptr(ersatt av unique_ptri C++11 ).

Exempel

Ett exempel på en C++-klass som implementerar resursfångst under initiering:

#include <cstdio> #inkludera <stdexcept> klassfil { _ offentliga : fil ( const char * filnamn ) : m_file_handle ( std :: fopen ( filnamn , "w+" ) )) { if ( ! m_file_handle ) throw std :: runtime_error ( "filöppningsfel" ) ; } ~ fil () { if ( std :: fclose ( m_file_handle ) != 0 ) { // fclose() kan returnera ett fel när du skriver de senaste ändringarna till disken } } void write ( const char * str ) { if ( std :: fputs ( str , m_file_handle ) == EOF ) throw std :: runtime_error ( "filskrivfel" ) ; } privat : std :: FIL * m_fil_handtag ; // Kopiera och tilldela inte implementerat. Förhindra deras användning genom att // förklara motsvarande metoder privata. fil ( const fil & ) ; fil & operator = ( const fil & ) ; }; // ett exempel på att använda denna klass void example_usage () { // öppna filen (ta tag i resursen) fil loggfil ( "logfile.txt" ) ; loggfil . skriv ( "hej loggfil!" ) ; // fortsätt använda loggfilen... // Du kan kasta undantag eller avsluta funktionen utan att behöva oroa dig för att stänga filen; // den stängs automatiskt när loggfilvariabeln går utanför räckvidden. }

Kärnan i RAII-formspråket är att klassen kapslar in ägandet (fånga och släppa) för någon resurs - till exempel en öppen filbeskrivning. När instansobjekt av en sådan klass är automatiska variabler är det garanterat att när de går utanför räckvidden kommer deras destruktor att anropas - vilket betyder att resursen kommer att frigöras. I det här exemplet kommer filen att stängas korrekt även om anropet std::fopen()returnerar ett fel och ett undantag skapas. Dessutom, om klasskonstruktorn fileslutfördes korrekt, garanterar den att filen verkligen är öppen. Om ett fel uppstår när filen öppnas, skapar konstruktorn ett undantag.

Med RAII och automatiska variabler kan ägandet av flera resurser enkelt hanteras. Ordningen i vilken destruktörer anropas är den omvända ordningen i vilken konstruktörer anropas; förstöraren anropas endast om objektet har skapats fullständigt (det vill säga om konstruktören inte gjorde ett undantag).

Att använda RAII förenklar koden och hjälper till att säkerställa att programmet fungerar korrekt.

En implementering utan undantag är möjlig (till exempel är detta nödvändigt i inbäddade applikationer). I det här fallet används standardkonstruktorn, som återställer filhanteraren, och en separat typmetod används för att öppna filen bool FileOpen(const char *). Innebörden av att använda en klass bevaras, speciellt om det finns flera utgångspunkter från metoden där ett objekt i klassen skapas. Naturligtvis, i det här fallet, kontrolleras behovet av att stänga filen i destruktorn.

Resursägandehantering utan RAII

I Java , som använder garbage collection , skapas objekt som refereras av automatiska variabler när det nya kommandot körs, och raderas av garbage collector, som körs automatiskt med obestämda intervaller. Det finns inga destruktörer i Java som garanterat kommer att anropas när en variabel går utanför räckvidden, och de färdigställare som finns tillgängliga i språket för att frigöra andra resurser än minne är inte lämpliga, eftersom det inte är känt när objektet kommer att raderas och om det kommer att raderas överhuvudtaget. Därför måste programmeraren själv ta hand om frigörandet av resurser. Det tidigare Java-exemplet kan skrivas om så här:

void java_example () { // öppen fil (hämta resurs) final LogFile logfile = new LogFile ( "logfile.txt" ) ; försök med { loggfil . skriv ( "hej loggfil!" ) ; // fortsätt använda loggfilen... // Du kan skapa undantag utan att behöva oroa dig för att stänga filen. // Filen kommer att stängas när finalblocket exekveras, vilket // garanterat körs efter försöksblocket även om //-undantag inträffar. } finally { // släpp loggfilresursen uttryckligen . stäng (); } }

Här ligger bördan av att explicit släppa resurser på programmeraren, vid varje punkt i koden där en resurs grips. Java 7 introducerade "prova-med-resurser"-konstruktionen som syntaktisk socker:

void java_example () { // öppna filen (ta tag i resursen) i huvudet på try-konstruktionen. // loggfilvariabeln finns bara inom detta block. försök ( LogFile logfile = new LogFile ( " logfile.txt " ) ) { loggfil . skriv ( "hej loggfil!" ) ; // fortsätt använda logfile... } // logfile.close() kommer automatiskt att anropas här, oavsett // eventuella undantag i kodblocket. }

För att den här koden ska fungera måste LogFile-klassen implementera systemgränssnittet java.lang.AutoCloseable och deklarera en void close();. Denna konstruktion är i själva verket en analog till using(){}C#-språkkonstruktionen, som också utför initieringen av en automatisk variabel av ett objekt och ett garanterat anrop till resursfrigöringsmetoden när denna variabel går utanför räckvidden.

Ruby och Smalltalk stöder inte RAII, men de har ett liknande kodningsmönster där metoder överför resurser till stängningsblock. Här är ett exempel i Ruby:

fil . öppna ( "logfile.txt" , "w+" ) gör | loggfil | loggfil . skriv ( "hej loggfil!" ) end # Metoden 'öppna' garanterar att filen stängs utan någon # explicit åtgärd från koden som skriver till filen

Operatören ' with' i Python , operatorn ' using' i C# och Visual Basic 2005 ger deterministisk kontroll över ägandet av resurser inom ett block och ersätter blocket finally, ungefär som i Ruby.

I Perl bestäms livslängden för objekt med hjälp av referensräkning , vilket gör att du kan implementera RAII på samma sätt som i C ++: objekt som inte finns referenser raderas omedelbart och destruktorn anropas, vilket kan frigöra resursen. Men objektens livslängd är inte nödvändigtvis bunden till någon omfattning. Du kan till exempel skapa ett objekt inuti en funktion och sedan tilldela en referens till det till någon global variabel, och därigenom öka objektets livslängd med en obestämd tid (och lämna resursen infångad för denna tid). Detta kan läcka resurser som borde ha frigjorts när föremålet gick utom räckvidd.

När du skriver kod i C krävs mer kod för att hantera resursägande eftersom den inte stöder undantag, försök-slutligen-block eller andra syntaxkonstruktioner som gör att du kan implementera RAII. Vanligtvis skrivs koden enligt följande schema: frisläppandet av resurser utförs i slutet av funktionen och en etikett placeras i början av denna kod; i mitten av funktionen, vid fel, bearbetas de, och sedan övergången till frigöring av resurser med operatören goto. I modern C, användningen av goto. Istället används konstruktioner mycket oftare if-else. Således dupliceras inte resursfrigivningskoden på varje felhanteringsplats inom en enskild funktion, utan den måste dupliceras i alla funktioner i filsäker stil.

int c_example () { int retval = 0 ; // returnera 0 om framgångsrik FIL * f = fopen ( "logfile.txt" , "w+" ); om ( f ) { do { // Öppnade filen framgångsrikt och arbetade med den if ( fputs ( "hej loggfil!" , f ) == EOF ) { retval = -2 ; bryta ; } // fortsätt använda resursen // ... } while ( 0 ); // Gratis resurser om ( fclose ( f ) == EOF ) { retval = -3 ; } } annan { // Det gick inte att öppna filen retval = -1 ; } returretval ; _ }

Det finns lite olika sätt att skriva sådan kod, men syftet med detta exempel är att visa idén i allmänhet.

Python-pseudokod

Du kan uttrycka idén om RAII i Python så här:

#coding: utf-8 resource_for_grep = Falsk klass RAII : g = globals () def __init__ ( self ): self . g [ 'resource_for_grep' ] = Sant def __del__ ( själv ): själv . g [ 'resource_for_grep' ] = Falskt print resource_for_grep #False r = RAII () print resource_for_grep #True del r print resource_for_grep #False

Perl-exempel

Källtext i Perl #!/usr/bin/perl -w =för kommentar Paketet implementerar designmönstret Resource Acquisition Is Initialization (RAII). Klassobjektet skapas och initieras endast när resursen förvärvas och raderas endast när resursen frigörs. =cut package Resurs { använd Scalar::Util qw/refaddr/ ; använd strikt ; använd varningar ; vårt $self = undef ; # ett externt tillgängligt objekt av denna klass (krävs för demonstration) mina %attributes ; # objektattributlager # -- ** konstruktor ** -- sub new { my ( $class , $resource ) = ( shift , shift ); mitt $själv = välsigna {}, $klass ; mitt $id = refaddr $self ; $attributes { $id }{ resurs } = undef ; # initiera det dolda fältet för objektet $self -> set_resource ( $resource ); # ställ in värdet på det dolda fältet return $self ; } # -- ** destructor ** -- sub del { my ( $self ) = ( shift ); $self = undef ; } # -- ** resursinitiering ** -- sub set_resource { my ( $self ) = ( shift ); $self -> { resurs } = skift ; } # -- ** hämta resurs ** -- sub get_resource { min $resurs = shift ; # namnet (i detta fall även värdet) på resursen inga strikta "refs" ; $self = & { __PACKAGE__ . '::new' }( __PACKAGE__ , $resurs ); # anropa klasskonstruktorn return $self -> { resurs }; # returresurs } # -- ** release resurs ** -- sub release_resource { my ( $resource ) = ( shift ); $self = $self -> del () if $self -> { resurs } eq $resurs ; # anropa destruktorn för en resurs med ett visst värde } } huvudpaket ; _ använd funktionen "säg" ; $resource = Resurs:: get_resource ( 'resurs' ); # anropa resurs och initialisera samtidigt säg $resurs ; # OUTPUT: resurs # resursvärde säg $ Resurs:: self ; # OUTPUT: Resurs=HASH(0x1ce4628) Resurs:: release_resource ( 'resurs' ); # släpp resursen säg $ Resource:: self ; # OUTPUT: Användning av oinitierat värde $Resource::self

Se även

Anteckningar