La adquisición de recursos es inicialización ( RAII ) [1] es un lenguaje de programación [2] que se utiliza en varios lenguajes de programación orientados a objetos y de tipo estático para describir el comportamiento de un lenguaje en particular. En RAII, mantener un recurso es una clase invariante y está vinculado a la vida útil del objeto : la asignación (o adquisición) de recursos se realiza durante la creación del objeto (específicamente la inicialización), por el constructor , mientras que la desasignación (liberación) de recursos se realiza durante la destrucción del objeto ( específicamente finalización), por el destructor. En otras palabras, la adquisición de recursos debe tener éxito para que la inicialización sea exitosa. Por lo tanto, se garantiza que el recurso se mantendrá entre el momento en que finaliza la inicialización y el inicio de la finalización (mantener los recursos es una clase invariante), y que se mantendrá solo cuando el objeto esté vivo. Por lo tanto, si no hay fugas de objetos, no hay fugas de recursos .
RAII se asocia principalmente con C ++ donde se originó, pero también con D , [3] Ada , [4] Vala , [5] y Rust . [6] La técnica fue desarrollada para la gestión de recursos segura para excepciones en C ++ [7] durante 1984–89, principalmente por Bjarne Stroustrup y Andrew Koenig , [8] y el término en sí fue acuñado por Stroustrup. [9] RAII se pronuncia generalmente como un inicialismo , a veces se pronuncia como "R, A, doble I". [10]
Otros nombres para este idioma incluyen Constructor Adquires, Destructor Releases (CADRe) [11] y un estilo particular de uso se llama Scope-based Resource Management (SBRM). [12] Este último término es para el caso especial de variables automáticas . RAII vincula los recursos a la vida útil del objeto , que puede no coincidir con la entrada y salida de un ámbito. (En particular, las variables asignadas en la tienda gratuita tienen una vida útil que no está relacionada con ningún alcance dado). Sin embargo, el uso de RAII para variables automáticas (SBRM) es el caso de uso más común.
Ejemplo de C ++ 11
El siguiente ejemplo de C ++ 11 demuestra el uso de RAII para el acceso a archivos y el bloqueo de mutex :
#include #include #include #include #include void WriteToFile ( const std :: string & message ) { // | mutex | es proteger el acceso a | archivo | (que se comparte entre hilos). static std :: mutex mutex ; // Bloquear | mutex | antes de acceder a | archivo |. std :: lock_guard < std :: mutex > lock ( mutex ); // Intenta abrir el archivo. std :: ofstream file ( "ejemplo.txt" ); if ( ! file . is_open ()) { throw std :: runtime_error ( "no se puede abrir el archivo" ); } // Escribir | mensaje | a | archivo |. archivo << mensaje << std :: endl ; // | archivo | se cerrará primero al salir del alcance (independientemente de la excepción) // el mutex se desbloqueará en segundo lugar (desde el destructor de bloqueo) al salir del alcance // (independientemente de la excepción). }
Este código es seguro para excepciones porque C ++ garantiza que todos los objetos de la pila se destruyen al final del ámbito adjunto, conocido como desenrollado de la pila . Por lo tanto, se garantiza que los destructores de los objetos de bloqueo y de archivo se llamarán al regresar de la función, ya sea que se haya lanzado una excepción o no. [13]
Las variables locales permiten una gestión sencilla de múltiples recursos dentro de una sola función: se destruyen en el orden inverso a su construcción, y un objeto se destruye sólo si está completamente construido, es decir, si no se propaga ninguna excepción desde su constructor. [14]
El uso de RAII simplifica enormemente la administración de recursos, reduce el tamaño general del código y ayuda a garantizar la corrección del programa. Por lo tanto, las directrices estándar de la industria recomiendan RAII, [15] y la mayor parte de la biblioteca estándar de C ++ sigue el idioma. [dieciséis]
Beneficios
Las ventajas de RAII como técnica de gestión de recursos son que proporciona encapsulación, seguridad de excepción (para recursos de pila) y localidad (permite que la lógica de adquisición y liberación se escriba una al lado de la otra).
La encapsulación se proporciona porque la lógica de gestión de recursos se define una vez en la clase, no en cada sitio de llamada. Se proporciona seguridad de excepción para los recursos de la pila (recursos que se liberan en el mismo ámbito en el que se adquieren) al vincular el recurso a la vida útil de una variable de la pila (una variable local declarada en un ámbito determinado): si se lanza una excepción , y el manejo de excepciones adecuado está en su lugar, el único código que se ejecutará al salir del alcance actual son los destructores de objetos declarados en ese alcance. Finalmente, la localidad de la definición se proporciona escribiendo las definiciones de constructor y destructor una al lado de la otra en la definición de clase.
Por lo tanto, la gestión de recursos debe estar vinculada a la vida útil de los objetos adecuados para obtener una asignación y recuperación automáticas. Los recursos se adquieren durante la inicialización, cuando no hay posibilidad de que se utilicen antes de que estén disponibles, y se liberan con la destrucción de los mismos objetos, lo que está garantizado incluso en caso de errores.
Comparando RAII con la finally
construcción utilizada en Java, Stroustrup escribió que “En sistemas realistas, hay muchas más adquisiciones de recursos que tipos de recursos, por lo que la técnica de 'adquisición de recursos es inicialización' conduce a menos código que el uso de una construcción 'finalmente'. " [1]
Usos típicos
El diseño RAII se usa a menudo para controlar bloqueos mutex en aplicaciones multiproceso . En ese uso, el objeto libera el candado cuando se destruye. Sin RAII en este escenario, el potencial de bloqueo mutuo sería alto y la lógica para bloquear el mutex estaría lejos de la lógica para desbloquearlo. Con RAII, el código que bloquea el mutex incluye esencialmente la lógica de que el bloqueo se liberará cuando la ejecución abandone el alcance del objeto RAII.
Otro ejemplo típico es la interacción con archivos: podríamos tener un objeto que representa un archivo que está abierto para escritura, donde el archivo se abre en el constructor y se cierra cuando la ejecución sale del alcance del objeto. En ambos casos, RAII solo asegura que el recurso en cuestión se libera adecuadamente; aún se debe tener cuidado para mantener la seguridad de excepción. Si el código que modifica la estructura de datos o el archivo no es seguro para excepciones, el mutex podría desbloquearse o el archivo cerrarse con la estructura de datos o el archivo dañado.
La propiedad de los objetos asignados dinámicamente (memoria asignada new
en C ++) también se puede controlar con RAII, de modo que el objeto se libera cuando se destruye el objeto RAII (basado en pila). Para este propósito, la biblioteca estándar de C ++ 11 define las clases de puntero inteligentestd::unique_ptr
para objetos de propiedad única y std::shared_ptr
para objetos con propiedad compartida. También hay clases similares disponibles std::auto_ptr
en C ++ 98 y boost::shared_ptr
en las bibliotecas de Boost .
Extensiones de "limpieza" del compilador
Tanto Clang como GNU Compiler Collection implementan una extensión no estándar del lenguaje C para admitir RAII: el atributo de variable "cleanup". [17] La siguiente macro anota una variable con una función destructora dada a la que llamará cuando la variable salga del alcance:
estático en línea void fclosep ( ARCHIVO ** fp ) { if ( * fp ) fclose ( * fp ); } #define _cleanup_fclose_ __attribute __ ((limpieza (fclosep)))
Esta macro se puede utilizar de la siguiente manera:
void example_usage () { _cleanup_fclose_ FILE * logfile = fopen ( "logfile.txt" , "w +" ); fputs ( "¡hola archivo de registro!" , archivo de registro ); }
En este ejemplo, el compilador hace que se llame a la función fclosep en el archivo de registro antes de que devuelva example_usage .
Limitaciones
RAII sólo funciona para los recursos adquiridos y liberados (directa o indirectamente) por objetos pila asignados, donde no es un objeto estático vida bien definido. Los objetos asignados al montón que adquieren y liberan recursos por sí mismos son comunes en muchos lenguajes, incluido C ++. RAII depende de que los objetos basados en el montón se eliminen implícita o explícitamente a lo largo de todas las posibles rutas de ejecución, para activar su destructor de liberación de recursos (o equivalente). [18] : 8:27 Esto se puede lograr mediante el uso de punteros inteligentes para administrar todos los objetos del montón, con punteros débiles para los objetos referenciados cíclicamente.
En C ++, solo se garantiza que se produzca el desenrollado de la pila si la excepción se detecta en algún lugar. Esto se debe a que "Si no se encuentra un controlador coincidente en un programa, se llama a la función terminate (); se desenrolle o no la pila antes de que esta llamada a terminate () esté definida por la implementación (15.5.1)". (Estándar C ++ 03, §15.3 / 9). [19] Este comportamiento suele ser aceptable, ya que el sistema operativo libera los recursos restantes como memoria, archivos, sockets, etc. al finalizar el programa. [ cita requerida ]
Recuento de referencias
Perl , Python (en la implementación de CPython ), [20] y PHP [21] gestionan la vida útil del objeto mediante el recuento de referencias , lo que hace posible utilizar RAII. Los objetos a los que ya no se hace referencia se destruyen o finalizan y liberan inmediatamente, por lo que un destructor o finalizador puede liberar el recurso en ese momento. Sin embargo, no siempre es idiomático en tales lenguajes, y se desaconseja específicamente en Python (a favor de los administradores de contexto y finalizadores del paquete thinref ).
Sin embargo, la vida útil de los objetos no está necesariamente vinculada a ningún ámbito, y los objetos pueden destruirse de forma no determinista o no destruirse en absoluto. Esto hace posible filtrar accidentalmente recursos que deberían haberse liberado al final de algún alcance. Es posible que los objetos almacenados en una variable estática (en particular, una variable global ) no se finalicen cuando finaliza el programa, por lo que sus recursos no se liberan; CPython no garantiza la finalización de dichos objetos, por ejemplo. Además, los objetos con referencias circulares no se recopilarán mediante un simple contador de referencias y vivirán indeterminadamente; incluso si se recopila (mediante una recolección de basura más sofisticada), el tiempo de destrucción y el orden de destrucción no serán deterministas. En CPython hay un detector de ciclos que detecta ciclos y finaliza los objetos en el ciclo, aunque antes de CPython 3.4, los ciclos no se recopilan si algún objeto en el ciclo tiene un finalizador. [22]
Referencias
- ↑ a b Stroustrup, Bjarne (30 de septiembre de 2017). "¿Por qué C ++ no proporciona una construcción" finalmente "? . Consultado el 9 de marzo de 2019 .
- ^ Sutter, Herb ; Alexandrescu, Andrei (2005). Estándares de codificación C ++ . Serie en profundidad de C ++. Addison-Wesley. pag. 24 . ISBN 978-0-321-11358-0.
- ^ "Protectores de alcance" . Tour de Dlang . Consultado el 21 de mayo de 2021 .
- ^ "Gema # 70: El lenguaje de las cerraduras de alcance" . AdaCore . Consultado el 21 de mayo de 2021 .
- ^ El Proyecto Valadate. "Destrucción" . La versión 0.30 del Tutorial de Vala . Consultado el 21 de mayo de 2021 .
- ^ "RAII - Rust por ejemplo" . doc.rust-lang.org . Consultado el 22 de noviembre de 2020 .
- ^ Stroustrup 1994 , 16.5 Gestión de recursos, págs. 388-89.
- ^ Stroustrup 1994 , 16.1 Manejo de excepciones: Introducción, págs. 383-84.
- ^ Stroustrup 1994 , p. 389. Llamé a esta técnica "la adquisición de recursos es la inicialización".
- ^ Michael Burr (19 de septiembre de 2008). "¿Cómo se pronuncia RAII?" . Desbordamiento de pila . Consultado el 9 de marzo de 2019 .
- ^ Arthur Tchaikovsky (6 de noviembre de 2012). "Cambiar RAII oficial a CADRe" . Norma ISO C ++ - Propuestas futuras . Grupos de Google . Consultado el 9 de marzo de 2019 .
- ^ Chou, Allen (1 de octubre de 2014). "Gestión de recursos basada en el alcance (RAII)" . Consultado el 9 de marzo de 2019 .
- ^ "¿Cómo puedo manejar un destructor que falla?" . Base C ++ estándar . Consultado el 9 de marzo de 2019 .
- ^ Richard Smith (21 de marzo de 2017). "Borrador de trabajo, estándar para ProgrammingLanguage C ++" (PDF) . Consultado el 9 de marzo de 2019 .
- ^ Stroustrup, Bjarne ; Sutter, Herb (3 de agosto de 2020). "Directrices básicas de C ++" . Consultado el 15 de agosto de 2020 .
- ^ "Tengo demasiados bloques de prueba; ¿qué puedo hacer al respecto?" . Base C ++ estándar . Consultado el 9 de marzo de 2019 .
- ^ "Especificación de atributos de variables" . Usando la colección de compiladores GNU (GCC) . Proyecto GNU . Consultado el 9 de marzo de 2019 .
- ^ Weimer, Westley; Necula, George C. (2008). "Situaciones excepcionales y confiabilidad del programa" (PDF) . Transacciones ACM sobre lenguajes y sistemas de programación . 30 (2).
- ^ ildjarn (5 de abril de 2011). "RAII y Stack desenrollando" . Desbordamiento de pila . Consultado el 9 de marzo de 2019 .
- ^ "Ampliación de Python con C o C ++: recuentos de referencias" . Ampliación e incorporación del intérprete de Python . Fundación de software Python . Consultado el 9 de marzo de 2019 .
- ^ hobbs (8 de febrero de 2011). "¿PHP es compatible con el patrón RAII? ¿Cómo?" . Consultado el 9 de marzo de 2019 .
- ^ "gc - Interfaz del recolector de basura" . La biblioteca estándar de Python . Fundación de software Python . Consultado el 9 de marzo de 2019 .
Otras lecturas
- Stroustrup, Bjarne (1994). El diseño y la evolución de C ++ . Addison-Wesley. Bibcode : 1994dec..book ..... S . ISBN 978-0-201-54330-8.
enlaces externos
- Ejemplo de capítulo: " Gotcha # 67: No emplear la adquisición de recursos es la inicialización " por Stephen C. Dewhurst
- Entrevista: " Una conversación con Bjarne Stroustrup " por Bill Venners
- Artículo: " La ley de los dos grandes " por Bjorn Karlsson y Matthew Wilson
- Artículo: " Implementar el lenguaje 'Adquisición de recursos es inicialización' " por Danny Kalev
- Artículo: " RAII, objetos dinámicos y fábricas en C ++ " por Roland Pibinger
- RAII en Delphi: " RAII de una sola línea en Delphi " por Barry Kelly