stringtranslate.com

La adquisición de recursos es inicialización

La adquisición de recursos es inicialización ( RAII ) [1] es un modismo de programación [2] utilizado en varios lenguajes de programación orientados a objetos y de tipado estático para describir un comportamiento particular del lenguaje. En RAII, mantener un recurso es una invariante de clase y está ligado a la duración 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 la finalización), por el destructor . En otras palabras, la adquisición de recursos debe tener éxito para que la inicialización tenga éxito. Por lo tanto, se garantiza que el recurso se mantenga entre el momento en que finaliza la inicialización y el momento en que comienza la finalización (mantener los recursos es una invariante de clase) y que se mantenga 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 Ada , [3] Vala , [4] y Rust . [5] La técnica fue desarrollada para la gestión de recursos segura ante excepciones en C++ [6] durante 1984-89, principalmente por Bjarne Stroustrup y Andrew Koenig , [7] y el término en sí fue acuñado por Stroustrup. [8]

Otros nombres para este modismo incluyen Constructor Acquires, Destructor Releases (CADRe) [9] y un estilo particular de uso se llama Scope-based Resource Management (SBRM). [10] 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. (Cabe destacar que las variables asignadas en el almacén libre tienen vidas útiles no relacionadas con ningún ámbito 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 <fstream> #include <iostream> #include <mutex> #include <stdexcept> #include <cadena>     void WriteToFile ( const std :: string & message ) { // |mutex| es para proteger el acceso a |file| (que se comparte entre subprocesos). static std :: mutex mutex ;         // Bloquea |mutex| antes de acceder a |archivo|. std :: lock_guard < std :: mutex > lock ( mutex );   // Intenta abrir el archivo. std :: ofstream file ( "example.txt" ); if ( ! file.is_open ( ) ) { throw std :: runtime_error ( "no se puede abrir el archivo" ); }         // Escribe |mensaje| en |archivo|. archivo << mensaje << std :: endl ;      // |file| se cerrará primero al salir del ámbito (sin importar la excepción) // |mutex| se desbloqueará en segundo lugar (desde el destructor |lock|) al salir del ámbito // (sin importar la excepción). }  

Este código es seguro ante excepciones porque C++ garantiza que todos los objetos con duración de almacenamiento automática (variables locales) se destruyen al final del ámbito que los contiene en el orden inverso al de su construcción. [11] 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, independientemente de si se ha lanzado una excepción o no. [12]

Las variables locales permiten una gestión sencilla de múltiples recursos dentro de una única función: se destruyen en el orden inverso a su construcción, y un objeto se destruye solo si está completamente construido, es decir, si no se propaga ninguna excepción desde su constructor. [13]

El uso de RAII simplifica enormemente la gestión de recursos, reduce el tamaño total del código y ayuda a garantizar la corrección del programa. Por lo tanto, las pautas estándar de la industria recomiendan el uso de RAII [14] y la mayor parte de la biblioteca estándar de C++ sigue este modelo. [15]

Beneficios

Las ventajas de RAII como técnica de gestión de recursos son que proporciona encapsulación, seguridad de excepciones (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).

Se proporciona encapsulación porque la lógica de administració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 pila (recursos que se liberan en el mismo ámbito en el que se adquieren) al vincular el recurso a la duración de una variable de pila (una variable local declarada en un ámbito determinado): si se lanza una excepción y se implementa el manejo adecuado de excepciones, el único código que se ejecutará al salir del ámbito actual son los destructores de los objetos declarados en ese ámbito. Finalmente, se proporciona localidad de definición al escribir 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 lograr 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 se garantiza que se llevará a cabo incluso en caso de errores.

Al comparar RAII con la finallyconstrucció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 'la 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 utiliza a menudo para controlar bloqueos mutex en aplicaciones multiproceso . En ese uso, el objeto libera el bloqueo cuando se destruye. Sin RAII en este escenario, el potencial de interbloqueo 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 salga del alcance del objeto RAII.

Otro ejemplo típico es la interacción con archivos: podríamos tener un objeto que represente un archivo que esté abierto para escritura, en donde el archivo se abre en el constructor y se cierra cuando la ejecución sale del ámbito del objeto. En ambos casos, RAII solo garantiza que el recurso en cuestión se libere de manera apropiada; aún se debe tener cuidado para mantener la seguridad de excepciones. 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 podría cerrarse con la estructura de datos o el archivo dañados.

La propiedad de objetos asignados dinámicamente (memoria asignada newen C++) también se puede controlar con RAII, de modo que el objeto se libere cuando se destruya 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_ptrpara objetos con propiedad compartida. También hay clases similares disponibles std::auto_ptren C++98 y boost::shared_ptren las bibliotecas Boost .

Además, se pueden enviar mensajes a recursos de red mediante RAII. En este caso, el objeto RAII enviaría un mensaje a un socket al final del constructor, cuando se complete su inicialización. También enviaría un mensaje al comienzo del destructor, cuando el objeto esté a punto de ser destruido. Una construcción de este tipo se podría utilizar en un objeto cliente para establecer una conexión con un servidor que se ejecuta en otro proceso.

Extensiones de "limpieza" del compilador

Tanto Clang como GNU Compiler Collection implementan una extensión no estándar del lenguaje C para soportar RAII: el atributo de variable "cleanup". [16] Lo siguiente anota una variable con una función destructora dada que llamará cuando la variable quede fuera de alcance:

void ejemplo_uso () { __atributo__ (( cleanup ( fclosep ))) ARCHIVO * archivo_de_registro = fopen ( "archivo_de_registro.txt" , "w+" ); fputs ( "¡Hola archivo_de_registro!" , archivo_de_registro ); }          

En este ejemplo, el compilador organiza que se llame a la función fclosep en el archivo de registro antes de que example_usage regrese.

Limitaciones

RAII solo funciona para recursos adquiridos y liberados (directa o indirectamente) por objetos asignados a la pila, donde hay un tiempo de vida de objeto estático bien definido. Los objetos asignados al montón que adquieren y liberan recursos 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 rutas de ejecución posibles, para activar su destructor de liberación de recursos (o equivalente). [17] : 8:27  Esto se puede lograr utilizando punteros inteligentes para administrar todos los objetos del montón, con punteros débiles para objetos referenciados cíclicamente.

En C++, solo se garantiza que se produzca el desenrollado de la pila si se captura la excepción 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 endsup(); independientemente de que la pila se desenrolle o no antes de esta llamada a endsup(), esto está definido por la implementación (15.5.1)" (estándar C++03, §15.3/9). [18] 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 ]

En la conferencia Gamelab de 2018, Jonathan Blow explicó cómo el uso de RAII puede causar fragmentación de memoria , lo que a su vez puede causar fallas de caché y una pérdida de rendimiento de 100 veces o peor . [19]

Recuento de referencias

Perl , Python (en la implementación CPython ), [20] y PHP [21] administran la duración de los objetos mediante el conteo de referencias , lo que hace posible el uso de 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 dichos lenguajes y se desaconseja específicamente en Python (a favor de los administradores de contexto y finalizadores del paquete weakref ). [ cita requerida ]

Sin embargo, la duración de los objetos no está necesariamente ligada a ningún ámbito, y los objetos pueden ser destruidos de forma no determinista o no ser destruidos en absoluto. Esto hace posible la fuga accidental de recursos que deberían haber sido liberados al final de algún ámbito. Los objetos almacenados en una variable estática (en particular una variable global ) pueden no ser finalizados cuando el programa termina, por lo que sus recursos no son liberados; CPython no garantiza la finalización de dichos objetos, por ejemplo. Además, los objetos con referencias circulares no serán recolectados por un simple contador de referencias, y vivirán indeterminadamente; incluso si son recolectados (por una recolección de basura más sofisticada), el tiempo de destrucción y el orden de destrucción serán no 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 recolectan si algún objeto en el ciclo tiene un finalizador. [22]

Referencias

  1. ^ ab Stroustrup, Bjarne (30 de septiembre de 2017). "¿Por qué C++ no proporciona una construcción "finalmente"?" . Consultado el 9 de marzo de 2019 .
  2. ^ Sutter, Herb ; Alexandrescu, Andrei (2005). Estándares de codificación de C++ . Serie C++ en profundidad. Addison-Wesley. p. 24. ISBN 978-0-321-11358-0.
  3. ^ "Gema n.° 70: el lenguaje de los bloqueos de alcance". AdaCore . Consultado el 21 de mayo de 2021 .
  4. ^ El Proyecto Valadate. "Destrucción". Tutorial de Vala versión 0.30 . Consultado el 21 de mayo de 2021 .
  5. ^ "RAII - Rust con ejemplos". doc.rust-lang.org . Consultado el 22 de noviembre de 2020 .
  6. ^ Stroustrup 1994, 16.5 Gestión de recursos, págs. 388–89.
  7. ^ Stroustrup 1994, 16.1 Manejo de excepciones: Introducción, págs. 383–84.
  8. ^ Stroustrup 1994, p. 389. Llamé a esta técnica "la adquisición de recursos es inicialización".
  9. ^ Arthur Tchaikovsky (6 de noviembre de 2012). "Cambio de RAII oficial a CADRe". Estándar ISO C++: propuestas futuras . Grupos de Google . Consultado el 9 de marzo de 2019 .
  10. ^ Chou, Allen (1 de octubre de 2014). "Gestión de recursos basada en el alcance (RAII)" . Consultado el 9 de marzo de 2019 .
  11. ^ Richard Smith (21 de marzo de 2017). "Borrador de trabajo, estándar para el lenguaje de programación C++" (PDF) . pág. 151, sección §9.6 . Consultado el 7 de septiembre de 2023 .
  12. ^ "¿Cómo puedo manejar un destructor que falla?". Standard C++ Foundation . Consultado el 9 de marzo de 2019 .
  13. ^ Richard Smith (21 de marzo de 2017). "Borrador de trabajo, estándar para el lenguaje de programación C++" (PDF) . Consultado el 9 de marzo de 2019 .
  14. ^ Stroustrup, Bjarne ; Sutter, hierba (3 de agosto de 2020). "Pautas básicas de C++" . Consultado el 15 de agosto de 2020 .
  15. ^ "Tengo demasiados bloques try; ¿qué puedo hacer al respecto?". Standard C++ Foundation . Consultado el 9 de marzo de 2019 .
  16. ^ "Especificación de atributos de variables". Uso de la Colección de compiladores GNU (GCC) . Proyecto GNU . Consultado el 9 de marzo de 2019 .
  17. ^ Weimer, Westley; Necula, George C. (2008). "Situaciones excepcionales y confiabilidad de programas" (PDF) . ACM Transactions on Programming Languages ​​and Systems . Vol. 30, núm. 2.
  18. ^ ildjarn (5 de abril de 2011). «RAII y desenrollado de pila». Stack Overflow . Consultado el 9 de marzo de 2019 .
  19. ^ Gamelab2018 - Las decisiones de diseño de Jon Blow sobre la creación de Jai, un nuevo lenguaje para programadores de juegos en YouTube
  20. ^ "Extensión de Python con C o C++: recuento de referencias". Extensión e incrustación del intérprete de Python . Python Software Foundation . Consultado el 9 de marzo de 2019 .
  21. ^ hobbs (8 de febrero de 2011). "¿PHP admite el patrón RAII? ¿Cómo?" . Consultado el 9 de marzo de 2019 .
  22. ^ "gc — Interfaz del recolector de basura". La biblioteca estándar de Python . Python Software Foundation . Consultado el 9 de marzo de 2019 .

Lectura adicional

Enlaces externos