La adquisición de recursos es la inicialización

La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la versión revisada el 25 de abril de 2019; las comprobaciones requieren 10 ediciones .

Obtener un recurso es la inicialización ( ing.  Resource Acquisition Is Initialization (RAII) ) es una expresión de software , cuyo significado radica en el hecho de que, con la ayuda de ciertos mecanismos de software, la obtención de un determinado recurso se combina inextricablemente con la inicialización y la liberación - con la destrucción del objeto.

Una forma típica (aunque no la única) de implementarlo es organizar el acceso al recurso en el constructor y la liberación en el destructor de la clase correspondiente. En muchos lenguajes de programación, como C++ , el destructor de una variable se llama inmediatamente al salir de su alcance cuando el recurso necesita ser liberado. Esto le permite garantizar la liberación del recurso cuando ocurre una excepción : el código se vuelve seguro en caso de excepciones ( seguridad de excepciones en inglés  ).

En los lenguajes que usan un recolector de basura , un objeto continúa existiendo mientras se le haga referencia .

Aplicaciones

Este concepto se puede utilizar para cualquier objeto o recurso compartido:

Un caso de uso importante para RAII son los "punteros inteligentes" : clases que encapsulan la propiedad de la memoria . Por ejemplo, hay una clase en la biblioteca de plantillas estándar de C ++ para este propósito (reemplazada por en C++11 ). auto_ptrunique_ptr

Ejemplo

Un ejemplo de una clase de C++ que implementa la captura de recursos durante la inicialización:

#incluir <cstdio> #incluir <stdexcept> archivo de clase { público : archivo ( const char * nombre de archivo ) : m_file_handle ( std :: fopen ( nombre de archivo , "w+" ) )) { si ( ! m_file_handle ) throw std :: runtime_error ( "error al abrir el archivo" ) ; } ~ archivo () { if ( std :: fclose ( m_file_handle ) != 0 ) { // fclose() puede devolver un error al escribir los últimos cambios en el disco } } escritura vacía ( const char * str ) { if ( std :: fputs ( str , m_file_handle ) == EOF ) throw std :: runtime_error ( "error de escritura del archivo" ) ; } privado : std :: ARCHIVO * m_file_handle ; // Copiar y asignar no implementado. Evite su uso // declarando privados los métodos correspondientes. archivo ( const archivo & ) ; archivo & operador = ( const archivo & ) ; }; // un ejemplo del uso de esta clase void ejemplo_uso () { // abrir el archivo (tomar el recurso) file logfile ( "logfile.txt" ) ; archivo de registro write ( "¡hola, archivo de registro!" ) ; // sigue usando el archivo de registro... // Puedes lanzar excepciones o salir de la función sin preocuparte por cerrar el archivo; // se cerrará automáticamente cuando la variable del archivo de registro quede fuera de alcance. }

La esencia del lenguaje RAII es que la clase encapsula la propiedad (captura y liberación) de algún recurso, por ejemplo, un descriptor de archivo abierto. Cuando los objetos de instancia de dicha clase son variables automáticas, se garantiza que cuando salgan del alcance, se llamará a su destructor, lo que significa que el recurso se liberará. En este ejemplo, el archivo se cerrará correctamente incluso si la llamada std::fopen()devuelve un error y se lanza una excepción. Además, si el constructor de la clase se filecompletó correctamente, garantiza que el archivo está realmente abierto. Si ocurre un error al abrir el archivo, el constructor lanza una excepción.

Con RAII y variables automáticas, la propiedad de múltiples recursos se puede administrar fácilmente. El orden en que se llaman los destructores es el inverso al orden en que se llaman los constructores; se llama al destructor solo si el objeto se ha creado por completo (es decir, si el constructor no lanzó una excepción).

El uso de RAII simplifica el código y ayuda a garantizar que el programa funcione correctamente.

Es posible una implementación sin excepciones (por ejemplo, esto es necesario en aplicaciones integradas). En este caso, se utiliza el constructor predeterminado, que restablece el controlador de archivos, y se utiliza un método de tipo independiente para abrir el archivo bool FileOpen(const char *). El significado de usar una clase se conserva, especialmente si hay varios puntos de salida del método donde se crea un objeto de la clase. Naturalmente, en este caso, la necesidad de cerrar el archivo se verifica en el destructor.

Gestión de propiedad de recursos sin RAII

En Java , que utiliza la recolección de basura , los objetos a los que hacen referencia las variables automáticas se crean cuando se ejecuta el nuevo comando y el recolector de basura los elimina, que se ejecuta automáticamente a intervalos indefinidos. No hay destructores en Java a los que se garantice que se llamará cuando una variable se sale del alcance, y los finalizadores disponibles en el lenguaje para liberar recursos distintos de la memoria no son adecuados, ya que no se sabe cuándo se eliminará el objeto y si se eliminará en absoluto. Por lo tanto, el programador debe encargarse él mismo de la liberación de recursos. El ejemplo anterior de Java se puede reescribir así:

void java_example () { // archivo abierto (recurso de captura) LogFile final logfile = new LogFile ( "logfile.txt" ) ; intente { archivo de registro . write ( "¡hola, archivo de registro!" ) ; // sigue usando el archivo de registro... // Puedes lanzar excepciones sin preocuparte por cerrar el archivo. // El archivo se cerrará cuando se ejecute el bloque finalmente, que // se garantiza que se ejecutará después del bloque de prueba, incluso si ocurren // excepciones. } finalmente { // libera explícitamente el recurso del archivo de registro . cerrar (); } }

Aquí, la carga de liberar recursos explícitamente recae en el programador, en cada punto del código donde se toma un recurso. Java 7 introdujo la construcción "probar con recursos" como azúcar sintáctico:

void java_example () { // abre el archivo (toma el recurso) en el encabezado de la construcción try. // la variable del archivo de registro solo existe dentro de este bloque. try ( LogFile logfile = new LogFile ( "logfile.txt" )) { logfile . write ( "¡hola, archivo de registro!" ) ; // seguir usando logfile... } // logfile.close() se llamará automáticamente aquí, independientemente de // cualquier excepción en el bloque de código. }

Para que este código funcione, la clase LogFile debe implementar la interfaz del sistema java.lang.AutoCloseable y declarar un archivo void close();. Esta construcción es, de hecho, un análogo de using(){}la construcción del lenguaje C#, que también realiza la inicialización de una variable automática por un objeto y una llamada garantizada al método de liberación de recursos cuando esta variable queda fuera del alcance.

Ruby y Smalltalk no son compatibles con RAII, pero tienen un patrón de codificación similar en el que los métodos pasan recursos a bloques de cierre. Aquí hay un ejemplo en Ruby:

archivo _ abrir ( "logfile.txt" , "w+" ) hacer | archivo de registro | archivo de registro write ( "¡hola, archivo de registro!" ) end # El método 'abrir' garantiza que el archivo se cerrará sin ninguna # acción explícita del código que escribe en el archivo

El operador ' with' en Python , el operador ' using' en C# y Visual Basic 2005 brindan un control determinista sobre la propiedad de los recursos dentro de un bloque y reemplazan el bloque finally, como en Ruby.

En Perl , la vida útil de los objetos se determina mediante el recuento de referencias , lo que le permite implementar RAII de la misma manera que en C ++: los objetos que no tienen referencias se eliminan inmediatamente y se llama al destructor, que puede liberar el recurso. Pero, la vida útil de los objetos no está necesariamente ligada a algún ámbito. Por ejemplo, puede crear un objeto dentro de una función y luego asignarle una referencia a alguna variable global, aumentando así la vida útil del objeto por una cantidad de tiempo indefinida (y dejando el recurso capturado por este tiempo). Esto puede filtrar recursos que deberían haberse liberado cuando el objeto salió del alcance.

Al escribir código en C , se requiere más código para administrar la propiedad de los recursos porque no admite excepciones, bloques de prueba final u otras construcciones de sintaxis que le permitan implementar RAII. Por lo general, el código se escribe de acuerdo con el siguiente esquema: la liberación de recursos se realiza al final de la función y se coloca una etiqueta al comienzo de este código; en el medio de la función, en caso de errores, se procesan y luego la transición a la liberación de recursos utilizando el operador goto. En C moderno, el uso de goto. En cambio, las construcciones se usan mucho más a menudo if-else. Por lo tanto, el código de liberación de recursos no se duplica en cada ubicación de manejo de errores dentro de una sola función, pero tendrá que duplicarse en todas las funciones de estilo seguro para archivos.

int c_ejemplo () { valor_int = 0 ; _ // devuelve 0 si tiene éxito ARCHIVO * f = fopen ( "logfile.txt" , "w+" ); si ( f ) { hacer { // Abrió con éxito el archivo, trabajando con él si ( fputs ( "¡hola, archivo de registro!" , f ) == EOF ) { valorretorno = -2 ; romper ; } // sigue usando el recurso // ... } while ( 0 ); // Recursos libres si ( fclose ( f ) == EOF ) { valorretorno = -3 ; } } más { // Error al abrir el archivo retval = -1 ; } retorno recuperación ; }

Hay formas ligeramente diferentes de escribir dicho código, pero el propósito de este ejemplo es mostrar la idea en general.

Pseudocódigo de Python

Puedes expresar la idea de RAII en Python así:

#codificación: utf-8 resource_for_grep = False class RAII : g = globales () def __init__ ( self ): self . g [ 'resource_for_grep' ] = True def __del__ ( self ): self . g [ 'recurso_para_grep' ] = Falso print resource_for_grep #False r = RAII () print resource_for_grep #True del r print resource_for_grep #False

Ejemplo de Perl

Texto fuente en Perl #!/usr/bin/perl -w =para comentario El paquete implementa el patrón de diseño Resource Acquisition Is Initialization (RAII). El objeto de clase se crea e inicializa solo cuando se adquiere el recurso y se elimina solo cuando se libera el recurso. =cortar paquete Recurso { use Scalar::Util qw/refaddr/ ; uso estricto ; advertencias de uso ; nuestro $auto = undef ; # un objeto accesible externamente de esta clase (requerido para demostración) my %attributes ; # almacén de atributos del objeto # -- ** constructor ** -- sub new { my ( $clase , $recurso ) = ( shift , shift ); mi $yo = bendecir {}, $clase ; mi $id = refaddr $self ; $atributos { $id }{ recurso } = undef ; # inicializar el campo oculto del objeto $self -> set_resource ( $resource ); # establecer el valor del campo oculto return $self ; } # -- ** destructor ** -- sub del { my ( $self ) = ( shift ); $auto = undef ; } # -- ** inicialización de recursos ** -- sub set_resource { my ( $self ) = ( shift ); $self -> { recurso } = shift ; } # -- ** obtener recurso ** -- sub get_resource { my $resource = shift ; # el nombre (en este caso también el valor) del recurso sin "refs" estrictos ; $auto = & { __PAQUETE__ . '::nuevo' }( __PAQUETE__ , $recurso ); # llamar al constructor de la clase return $self -> { recurso }; # devolver recurso } # -- ** recurso de liberación ** -- sub release_resource { my ( $resource ) = ( shift ); $self = $self -> del () if $self -> { recurso } eq $recurso ; # llamar al destructor de un recurso con un cierto valor } } paquete principal ; usar la función "decir" ; $recurso = Recurso:: get_resource ( 'recurso' ); # llamar al recurso e inicializar al mismo tiempo decir $recurso ; # SALIDA: recurso # valor del recurso decir $ Recurso:: self ; # SALIDA: Recurso=HASH(0x1ce4628) Recurso:: release_resource ( 'recurso' ); # libera el recurso say $ Resource:: self ; # SALIDA: Uso de valor no inicializado $Resource::self

Véase también

Notas