stringtranslate.com

La adquisición de recursos es la 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 de tipado estático y orientado a objetos para describir el comportamiento de un lenguaje particular. En RAII, mantener un recurso es una invariante de clase 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 parte del constructor , mientras que la desasignación (liberación) de recursos se realiza durante la destrucción del objeto (específicamente la finalización), por parte del 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 mantendrá entre el momento en que finaliza la inicialización y el inicio de la finalización (retener los recursos es una invariante de clase) y 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 más prominentemente 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 de uso particular se llama Gestión de recursos basada en alcance (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 alcance. (En particular, las variables asignadas en la tienda gratuita tienen vidas útiles que no están relacionadas con ningún ámbito determinado). 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 mutex :

#include <fstream> #include <iostream> #include <mutex> #include <stdexcept> #include <cadena>     void WriteToFile ( const std :: cadena y mensaje ) { // |mutex| es proteger el acceso a |archivo| (que se comparte entre hilos). estándar estático :: mutex mutex ;         // Bloquear |mutex| antes de acceder a |archivo|. std :: lock_guard < std :: mutex > bloqueo ( mutex );   // Intenta abrir el archivo. std :: archivo ofstream ( "ejemplo.txt" ); if ( ! file . is_open ()) { throw std :: runtime_error ( "no se puede abrir el archivo" ); }         // Escribir |mensaje| a |archivar|. archivo << mensaje << std :: endl ;      // |archivo| se cerrará primero al salir del alcance (independientemente de la excepción) // |mutex| se desbloqueará en segundo lugar (desde |lock| destructor) al salir del alcance // (independientemente de la excepción). }  

Este código es seguro para excepciones porque C++ garantiza que todos los objetos con duración de almacenamiento automático (variables locales) se destruyen al final del alcance adjunto en el orden inverso a su construcción. [11] Por lo tanto, se garantiza que los destructores de los objetos de bloqueo y de archivo serán llamados al regresar de la función, ya sea que se haya lanzado una excepción o no. [12]

Las variables locales permiten una fácil gestión 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 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 general del código y ayuda a garantizar la corrección del programa. Por lo tanto, RAII es recomendado por las pautas estándar de la industria, [14] y la mayor parte de la biblioteca estándar de C++ sigue este idioma. [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 escribir la lógica de adquisición y liberación 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 la pila (recursos que se liberan en el mismo ámbito en el que se adquieren) vinculando 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 Si se implementa un manejo de excepciones adecuado, el único código que se ejecutará al salir del alcance actual son los destructores de los objetos declarados en ese alcance. Finalmente, la localidad de 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 poder 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 se garantiza que se producirá incluso en caso de errores.

Comparando 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 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 utiliza a menudo para controlar bloqueos mutex en aplicaciones de subprocesos múltiples . 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 abandone el 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, donde el archivo se abre en el constructor y se cierra cuando la ejecución sale del alcance del objeto. En ambos casos, la RAII sólo garantiza que el recurso en cuestión se libere adecuadamente; aún se debe tener cuidado para mantener la seguridad excepcional. Si el código que modifica la estructura de datos o el archivo no es seguro para excepciones, el mutex podría desbloquearse o cerrarse el archivo con la estructura de datos o el archivo dañado.

La propiedad de los objetos asignados dinámicamente (memoria asignada en newC++) también se puede controlar con RAII, de modo que el objeto se libere cuando se destruya el objeto RAII (basado en pila). Para ello, la biblioteca estándar 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 de 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 podría usarse en un objeto de 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 admitir RAII: el atributo variable de "limpieza". [16] Lo siguiente anota una variable con una función destructora dada que llamará cuando la variable salga del alcance:

void example_usage () { __attribute__ (( limpieza ( fclosep ))) ARCHIVO * logfile = fopen ( "logfile.txt" , "w+" ); fputs ( "¡hola archivo de registro!" , archivo de registro ); }          

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

Limitaciones

RAII solo funciona para recursos adquiridos y liberados (directa o indirectamente) por objetos asignados en pila, donde existe una vida útil de objeto estático bien definida. Los objetos asignados en 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 mediante el uso de punteros inteligentes para administrar todos los objetos del montón, con punteros débiles para los objetos a los que se hace referencia cíclicamente.

En C++, solo se garantiza que la pila se desenrolle si la excepción se detecta en algún lugar. Esto se debe a que "Si no se encuentra ningún controlador coincidente en un programa, se llama a la función terminar(); si la pila se desenrolla o no antes de esta llamada a terminar() 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 necesaria ]

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

Conteo de referencias

Perl , Python (en la implementación CPython ), [20] y PHP [21] gestionan la vida útil de los objetos 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 dichos lenguajes y se desaconseja específicamente en Python (a favor de los administradores de contexto y finalizadores del paquete débilref ). [ cita necesaria ]

Sin embargo, la vida útil de los objetos no está necesariamente sujeta a ningún alcance y los objetos pueden destruirse de forma no determinista o no destruirse en absoluto. Esto hace posible que se filtren accidentalmente recursos que deberían haberse liberado al final de algún alcance. Los objetos almacenados en una variable estática (en particular, una variable global ) pueden no finalizarse 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 serán recopilados por un simple contador de referencias y vivirán indeterminadamente; incluso si se recolecta (mediante una recolección de basura más sofisticada), el tiempo y el orden de destrucción no serán deterministas. En CPython hay un detector de ciclos que detecta ciclos y finaliza los objetos del ciclo, aunque antes de CPython 3.4, los ciclos no se recopilan si algún objeto del ciclo tiene un finalizador. [22]

Referencias

  1. ^ ab Stroustrup, Bjarne (30 de septiembre de 2017). "¿Por qué C++ no proporciona una construcción "final"?" . Consultado el 9 de marzo de 2019 .
  2. ^ Sutter, hierba ; Alexandrescu, Andrei (2005). Estándares de codificación C++ . Serie en profundidad de C++. Addison-Wesley. pag. 24.ISBN 978-0-321-11358-0.
  3. ^ "Joya n.º 70: el lenguaje de bloqueos de alcance". AdaCore . Consultado el 21 de mayo de 2021 .
  4. ^ El Proyecto Valadate. "Destrucción". El tutorial de Vala versión 0.30 . Consultado el 21 de mayo de 2021 .
  5. ^ "RAII - Óxido con el ejemplo". 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.
  8. ^ Stroustrup 1994, pág. 389. Llamé a esta técnica "la adquisición de recursos es inicialización".
  9. ^ Arthur Tchaikovsky (6 de noviembre de 2012). "Cambiar 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) . pag. 151, sección §9.6 . Consultado el 7 de septiembre de 2023 .
  12. ^ "¿Cómo puedo manejar un destructor que falla?". Fundación C++ estándar . 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 de prueba; ¿qué puedo hacer al respecto?". Fundación C++ estándar . Consultado el 9 de marzo de 2019 .
  16. ^ "Especificar atributos de variables". Usando 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 del programa" (PDF) . Transacciones ACM sobre lenguajes y sistemas de programación . vol. 30, núm. 2.
  18. ^ ildjarn (5 de abril de 2011). "RAII y Stack desenrollado". Desbordamiento de pila . Consultado el 9 de marzo de 2019 .
  19. ^ Gamelab2018: decisiones de diseño de Jon Blow sobre la creación de Jai, un nuevo lenguaje para programadores de juegos en YouTube
  20. ^ "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 .
  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 . Fundación de software Python . Consultado el 9 de marzo de 2019 .

Otras lecturas

enlaces externos