Az erőforrás-beszerzés inicializálás

Az oldal jelenlegi verzióját még nem ellenőrizték tapasztalt közreműködők, és jelentősen eltérhet a 2019. április 25-én felülvizsgált verziótól ; az ellenőrzésekhez 10 szerkesztés szükséges .

Az erőforrás megszerzése inicializálás ( eng.  Resource Acquisition Is Initialization (RAII) ) egy szoftver idióma , melynek jelentése abban rejlik, hogy bizonyos szoftvermechanizmusok segítségével egy adott erőforrás megszerzése elválaszthatatlanul összekapcsolódik az inicializálással és a kiadással - a tárgy megsemmisítésével.

A megvalósítás tipikus (bár nem az egyetlen) módja az, hogy az erőforráshoz való hozzáférést a konstruktorban , a kiadást pedig a megfelelő osztály destruktorában szervezzük meg. Számos programozási nyelvben, mint például a C++ , a változó destruktora azonnal meghívásra kerül, amikor kilép a hatóköréből , amikor az erőforrást fel kell szabadítani. Ez lehetővé teszi, hogy garantálja az erőforrás felszabadítását, ha kivétel történik : a kód kivételek esetén biztonságossá válik ( angol  Exception safety ).

A szemétgyűjtőt használó nyelveken egy objektum mindaddig létezik, amíg hivatkoznak rá .

Alkalmazások

Ez a koncepció bármely megosztott objektumhoz vagy erőforráshoz használható:

A RAII egyik fontos felhasználási esete az "okos mutatók" : olyan osztályok, amelyek a memória tulajdonjogát foglalják magukba . Például a C ++ szabványos sablonkönyvtárban van egy osztály erre a célra ( a C++11-ben ezt helyettesíti ). auto_ptrunique_ptr

Példa

Példa egy C++ osztályra, amely erőforrás rögzítést valósít meg az inicializálás során:

#include <cstdio> #include <stdexcept> class fájl { nyilvános : file ( const char * fájlnév ) : m_file_handle ( std :: fopen ( fájlnév , "w+" ) )) { if ( ! m_file_handle ) throw std :: runtime_error ( "fájlmegnyitási hiba" ) ; } ~ fájl () { if ( std :: fclose ( m_file_handle ) != 0 ) { // Az fclose() hibát jelezhet a legutóbbi változtatások lemezre írásakor } } void write ( const char * str ) { if ( std :: fputs ( str , m_file_handle ) == EOF ) throw std :: runtime_error ( "fájlírási hiba" ) ; } privát : std :: FÁJL * m_file_handle ; // A másolás és a hozzárendelés nincs megvalósítva. Akadályozza meg használatukat úgy, hogy // a megfelelő módszereket privátnak nyilvánítja. fájl ( const fájl & ) ; fájl & operátor = ( const fájl & ) ; }; // egy példa az osztály használatára void example_usage () { // nyissa meg a fájlt (fogja meg az erőforrást) fájl logfile ( "logfile.txt" ) ; naplófájl . write ( "hello logfile!" ) ; // továbbra is használja a naplófájlt... // Kivételeket dobhat, vagy kiléphet a függvényből anélkül, hogy aggódnia kellene a fájl bezárása miatt; // automatikusan bezárul, ha a logfile változó kikerül a hatókörből. }

A RAII idióma lényege, hogy az osztály bizonyos erőforrások – például egy nyitott fájlleíró – tulajdonjogát (rögzítés és felszabadítás) tartalmazza. Ha egy ilyen osztály példányobjektumai automatikus változók, akkor garantáltan, hogy amikor kilépnek a hatókörből, a destruktoruk meghívódik - ami azt jelenti, hogy az erőforrás felszabadul. Ebben a példában a fájl akkor is megfelelően záródik be, ha a hívás std::fopen()hibát ad vissza, és kivételt ad. Sőt, ha az osztálykonstruktor filehelyesen fejezte be, akkor garantálja, hogy a fájl valóban meg van nyitva. Ha hiba történik a fájl megnyitásakor, a konstruktor kivételt dob.

A RAII és az automatikus változók segítségével több erőforrás tulajdonjoga könnyen kezelhető. A destruktorok meghívásának sorrendje fordítottja a konstruktorok meghívásának; a destruktor csak akkor hívódik meg, ha az objektumot teljesen létrehozták (vagyis ha a konstruktor nem dobott kivételt).

A RAII használata leegyszerűsíti a kódot, és segít biztosítani a program megfelelő működését.

Kivételek nélküli megvalósítás lehetséges (például beágyazott alkalmazásoknál ez szükséges). Ebben az esetben az alapértelmezett konstruktort használjuk, amely alaphelyzetbe állítja a fájlkezelőt, és egy külön típusmódszerrel nyitja meg a fájlt bool FileOpen(const char *). Az osztály használatának értelme megmarad, különösen, ha több kilépési pont is van a metódusból, ahol az osztály objektuma létrejön. Természetesen ebben az esetben a fájl bezárásának szükségességét a destruktor ellenőrzi.

Erőforrás-tulajdonkezelés RAII nélkül

A szemétgyűjtést használó Java -ban az automatikus változók által hivatkozott objektumok az új parancs végrehajtásakor jönnek létre, és a szemétgyűjtő törli őket, amely határozatlan időközönként automatikusan lefut. A Java-ban nincsenek olyan destruktorok, amelyek garantáltan meghívódnak, ha egy változó kikerül a hatókörből, és a nyelven elérhető véglegesítők a memórián kívüli erőforrások felszabadítására nem alkalmasak, mivel nem tudni, hogy az objektum mikor törlődik, és hogy nem egyáltalán törlésre kerül. Ezért a programozónak magának kell gondoskodnia az erőforrások felszabadításáról. Az előző Java példa így átírható:

void java_example () { // fájl megnyitása (erőforrás megragadása) final LogFile logfile = new LogFile ( "naplófájl.txt" ) ; try { logfile . write ( "hello logfile!" ) ; // továbbra is használja a naplófájlt... // Kivételeket dobhat anélkül, hogy aggódnia kellene a fájl bezárása miatt. // A fájl a végleges blokk végrehajtásakor záródik be, amely // garantáltan futni fog a try blokk után még akkor is, ha // kivételek lépnek fel. } végül { // kifejezetten engedje el a naplófájl erőforrást . bezár (); } }

Itt az erőforrások kifejezett felszabadítása a programozóra hárul, a kód minden pontján, ahol egy erőforrást megragadnak. A Java 7 bevezette a "try-with-resources" konstrukciót szintaktikai cukorként:

void java_example () { // nyissa meg a fájlt (fogja meg az erőforrást) a try konstrukció fejében. // a naplófájl változó csak ebben a blokkban létezik. try ( LogFile logfile = new LogFile ( "logfile.txt" )) { naplófájl . write ( "hello logfile!" ) ; // továbbra is használja a logfile-t... } // A logfile.close() automatikusan itt lesz meghívva, függetlenül // a kódblokkban lévő kivételektől. }

Ahhoz, hogy ez a kód működjön, a LogFile osztálynak megvalósítania kell a java.lang.AutoCloseable rendszerfelületet, és deklarálnia kell egy void close();. Ez a konstrukció valójában using(){}a C# nyelvi konstrukció analógja, amely egy automatikus változó inicializálását is végrehajtja egy objektum által, és garantáltan hívja az erőforrás-felszabadítási metódust, ha ez a változó kikerül a hatókörből.

A Ruby és a Smalltalk nem támogatja a RAII-t, de hasonló kódolási mintával rendelkeznek, amelyben a módszerek az erőforrásokat a lezáró blokkokba adják át. Íme egy példa a Ruby nyelven:

fájl . open ( "logfile.txt" , "w+" ) do | naplófájl | naplófájl . write ( "hello logfile!" ) end # Az 'open' metódus garantálja, hogy a fájl bezárásra kerül a fájlba író kód kifejezett # művelete nélkül

A withPythonban a ' ' operátor , a C# és a Visual Basic 2005 alatt a ' using' operátor determinisztikus vezérlést biztosít a blokkon belüli erőforrások tulajdonjogának felett, és lecseréli a blokkot , hasonlóan a Rubyhoz. finally

A Perlben az objektumok élettartamát a referenciaszámlálás segítségével határozzák meg , amely lehetővé teszi a RAII megvalósítását ugyanúgy, mint a C ++-ban: a hivatkozásokkal nem rendelkező objektumok azonnal törlődnek, és meghívásra kerül a destruktor, amely felszabadíthatja az erőforrást. Az objektumok élettartama azonban nem feltétlenül kötődik valamilyen hatókörhöz. Például létrehozhat egy objektumot egy függvényen belül, majd hivatkozást rendelhet hozzá valamilyen globális változóhoz, ezáltal meghosszabbítva az objektum élettartamát korlátlan ideig (és az erőforrást erre az időre rögzítve hagyja). Ez olyan erőforrásokat szivároghat ki, amelyeket fel kellett volna szabadítani, amikor az objektum kikerült a hatókörből.

Ha kódot ír C nyelven , több kódra van szükség az erőforrások tulajdonjogának kezeléséhez, mert nem támogatja a kivételeket, a try-finally blokkokat vagy más szintaktikai konstrukciókat, amelyek lehetővé teszik a RAII megvalósítását. Általában a kódot a következő séma szerint írják: az erőforrások felszabadítása a függvény végén történik, és egy címke kerül a kód elejére; a függvény közepén hiba esetén ezek feldolgozása, majd az erőforrások felszabadítására való áttérés az operátor segítségével történik goto. A modern C-ben a goto. Ehelyett sokkal gyakrabban használnak konstrukciókat if-else. Így az erőforrás-kibocsátási kód nem duplikálódik minden hibakezelési helyen egyetlen függvényen belül, hanem minden fájlbiztos stílusú függvényben meg kell duplikálni.

int c_example () { int retval = 0 ; // 0-t ad vissza, ha sikeres FILE * f = fopen ( "logfile.txt" , "w+" ); ha ( f ) { csináld { // Sikeresen megnyitottuk a fájlt, dolgoztunk vele if ( fputs ( "hello logfile!" , f ) == EOF ) { retval = -2 ; szünet ; } // továbbra is használja az erőforrást // ... } while ( 0 ); // Szabad erőforrások if ( fclose ( f ) == EOF ) { retval = -3 ; } } más { // Nem sikerült megnyitni a fájlt retval = -1 ; } return retval ; }

Az ilyen kódok írásának kissé eltérő módjai vannak, de ennek a példának a célja az ötlet általános bemutatása.

Python pszeudokód

A RAII ötletét Pythonban így fejezheti ki:

#coding: utf-8 resource_for_grep = False osztály RAII : g = globals () def __init__ ( self ): self . g [ 'resource_for_grep' ] = True def __del__ ( self ): self . g [ 'resource_for_grep' ] = Hamis print resource_for_grep #False r = RAII () print Resource_for_grep #True del r print Resource_for_grep #False

Perl példa

Forrásszöveg Perlben #!/usr/bin/perl -w =megjegyzéshez A csomag a Resource Acquisition Is Initialization (RAII) tervezési mintát valósítja meg. Az osztályobjektum csak az erőforrás beszerzésekor jön létre és inicializálódik, és csak az erőforrás felszabadításakor törlődik. =cut package Erőforrás { használd a Scalar ::Util qw/refaddr/ ; szigorúan használd ; figyelmeztetések használata ; $ énünk = undef ; # egy külsőleg elérhető objektum ebben az osztályban (szükséges a demonstrációhoz) my %attributes ; # objektum attribútumtároló # -- ** konstruktor ** -- sub new { my ( $osztály , $erőforrás ) = ( shift , shift ); én $én = áldja meg {}, $class ; my $id = refaddr $self ; $attribútumok { $id }{ erőforrás } = undef ; # inicializálja az objektum rejtett mezőjét $self -> set_resource ( $erőforrás ); # állítsa be a rejtett mező értékét return $self ; } # -- ** destructor ** -- sub del { my ( $self ) = ( shift ); $self = undef ; } # -- ** erőforrás inicializálása ** -- sub set_resource { my ( $self ) = ( shift ); $self -> { erőforrás } = shift ; } # -- ** get_resource ** -- sub get_resource { my $erőforrás = shift ; # az erőforrás neve (ebben az esetben az értéke is) nincs szigorú "refs" ; $self = & { __CSOMAG__ . '::new' }( __CSOMAG__ , $erőforrás ); # meghívja az osztálykonstruktort return $self -> { erőforrás }; # visszatérési erőforrás } # -- ** kiadási erőforrás ** -- sub release_resource { my ( $erőforrás ) = ( shift ); $self = $self -> del () if $self -> { erőforrás } eq $erőforrás ; # hívja a destruktort egy bizonyos értékű erőforráshoz } } csomag ; használja a "say" funkciót ; $erőforrás = Erőforrás:: get_resource ( 'erőforrás' ); # erőforrás hívása és inicializálása egyidejűleg mondjuk $resource ; # OUTPUT: erőforrás # erőforrás érték mond $ Erőforrás:: self ; # OUTPUT: Erőforrás=HASH(0x1ce4628) Erőforrás:: release_resource ( 'erőforrás' ); # engedje el az erőforrást mondjon $ Resource:: self ; # OUTPUT: inicializálatlan $Resource::self érték használata

Lásd még

Jegyzetek