¿Podrían los desarrolladores de C ++ darnos una buena descripción de qué es RAII, por qué es importante y si podría tener alguna relevancia para otros lenguajes?
Me hago saber un poco. Creo que significa "Adquisición de recursos es inicialización". Sin embargo, ese nombre no concuerda con mi comprensión (posiblemente incorrecta) de lo que es RAII: tengo la impresión de que RAII es una forma de inicializar objetos en la pila de modo que, cuando esas variables se salen del alcance, los destructores automáticamente ser llamado haciendo que los recursos se limpien.
Entonces, ¿por qué no se llama "usar la pila para activar la limpieza" (UTSTTC :)? ¿Cómo se llega desde allí a "RAII"?
¿Y cómo puedes hacer algo en la pila que provoque la limpieza de algo que vive en el montón? Además, ¿hay casos en los que no puede usar RAII? ¿Alguna vez ha deseado la recolección de basura? ¿Al menos un recolector de basura que podría usar para algunos objetos mientras deja que otros sean administrados?
Gracias.
fuente
:(
Estaba leyendo todo el hilo, en ese entonces, ¡y ni siquiera me consideraba un novato en C ++!Respuestas:
RAII le está diciendo qué hacer: ¡Adquiera su recurso en un constructor! Yo agregaría: un recurso, un constructor. UTSTTC es solo una aplicación de eso, RAII es mucho más.
La gestión de recursos apesta. Aquí, recurso es cualquier cosa que necesite limpieza después de su uso. Los estudios de proyectos en muchas plataformas muestran que la mayoría de los errores están relacionados con la administración de recursos, y es particularmente malo en Windows (debido a los muchos tipos de objetos y asignadores).
En C ++, la administración de recursos es particularmente complicada debido a la combinación de excepciones y plantillas (estilo C ++). Para echar un vistazo debajo del capó, consulte GOTW8 ).
C ++ garantiza que se llama al destructor si y solo si el constructor tuvo éxito. Confiando en eso, RAII puede resolver muchos problemas desagradables de los que el programador promedio ni siquiera es consciente. Aquí hay algunos ejemplos más allá de "mis variables locales serán destruidas cuando regrese".
Comencemos con una
FileHandle
clase demasiado simplista que emplea RAII:Si la construcción falla (con una excepción), no se llama a ninguna otra función miembro, ni siquiera al destructor.
RAII evita el uso de objetos en estado no válido. ya nos facilita la vida incluso antes de utilizar el objeto.
Ahora, echemos un vistazo a los objetos temporales:
Hay tres casos de error para manejar: no se puede abrir ningún archivo, solo se puede abrir un archivo, se pueden abrir ambos archivos pero no se pudo copiar. En una implementación no RAII,
Foo
tendría que manejar los tres casos de forma explícita.RAII libera recursos que se adquirieron, incluso cuando se adquieren varios recursos dentro de una declaración.
Ahora, agreguemos algunos objetos:
El constructor de
Logger
fallará sioriginal
el constructor de 'falla (porquefilename1
no se pudo abrir),duplex
el constructor de' falla (porquefilename2
no se pudo abrir), o la escritura en los archivos dentroLogger
del cuerpo del constructor falla. En cualquiera de estos casos, no se llamará alLogger
destructor de ' , por lo que no podemos confiar en el destructor de' para liberar los archivos. Pero si fue construido, su destructor será llamado durante la limpieza del constructor.Logger
original
Logger
RAII simplifica la limpieza después de la construcción parcial.
Puntos negativos:
¿Puntos negativos? Todos los problemas se pueden resolver con RAII y punteros inteligentes ;-)
RAII a veces es difícil de manejar cuando necesita una adquisición retrasada, empujando objetos agregados al montón.
Imagine que el registrador necesita un
SetTargetFile(const char* target)
. En ese caso, el identificador, que aún debe ser miembroLogger
, debe residir en el montón (por ejemplo, en un puntero inteligente, para desencadenar la destrucción del identificador de manera apropiada).Realmente nunca he deseado la recolección de basura. Cuando hago C #, a veces siento un momento de felicidad que simplemente no necesito preocuparme, pero mucho más extraño todos los juguetes geniales que se pueden crear a través de la destrucción determinista. (usar
IDisposable
simplemente no lo corta).He tenido una estructura particularmente compleja que podría haberse beneficiado de GC, donde los punteros inteligentes "simples" causarían referencias circulares sobre múltiples clases. Nos salimos del camino equilibrando cuidadosamente los indicadores fuertes y débiles, pero cada vez que queremos cambiar algo, tenemos que estudiar una gran tabla de relaciones. GC podría haber sido mejor, pero algunos de los componentes contenían recursos que deberían publicarse lo antes posible.
Una nota sobre la muestra de FileHandle: no se pretendía que fuera completa, solo una muestra, pero resultó incorrecta. Gracias Johannes Schaub por señalarlo y FredOverflow por convertirlo en una solución C ++ 0x correcta. Con el tiempo, me he conformado con el enfoque documentado aquí .
fuente
Foo
poseeBar
y loBoz
muta, ...Hay excelentes respuestas por ahí, así que solo agrego algunas cosas olvidadas.
0. RAII se trata de alcances
RAII se trata de ambos:
Otros ya respondieron sobre eso, así que no daré más detalles.
1. Al codificar en Java o C #, ya usa RAII ...
Como hizo Monsieur Jourdain con la prosa, C # e incluso la gente de Java ya usa RAII, pero de manera oculta. Por ejemplo, el siguiente código Java (que se escribe de la misma manera en C # reemplazando
synchronized
conlock
):... ya está usando RAII: La adquisición de mutex se realiza en la palabra clave (
synchronized
olock
), y la des-adquisición se realizará al salir del alcance.Es tan natural en su notación que casi no requiere explicación incluso para las personas que nunca han oído hablar de RAII.
La ventaja que tiene C ++ sobre Java y C # aquí es que se puede hacer cualquier cosa con RAII. Por ejemplo, no hay un equivalente integrado directo de
synchronized
nilock
en C ++, pero aún podemos tenerlos.En C ++, estaría escrito:
que se puede escribir fácilmente de forma Java / C # (usando macros C ++):
2. RAII tiene usos alternativos
Sabe cuándo se llamará al constructor (en la declaración del objeto), y sabe cuándo se llamará a su destructor correspondiente (a la salida del alcance), por lo que puede escribir código casi mágico con solo una línea. Bienvenido al país de las maravillas de C ++ (al menos, desde el punto de vista de un desarrollador de C ++).
Por ejemplo, puede escribir un objeto contador (lo dejé como un ejercicio) y usarlo simplemente declarando su variable, como se usó el objeto de bloqueo anterior:
que, por supuesto, se puede escribir, nuevamente, de la forma Java / C # usando una macro:
3. ¿Por qué falta C ++
finally
?La
finally
cláusula se usa en C # / Java para manejar la eliminación de recursos en caso de salida del alcance (ya sea a través de unareturn
excepción lanzada).Los lectores de especificaciones astutos habrán notado que C ++ no tiene una cláusula final. Y esto no es un error, porque C ++ no lo necesita, ya que RAII ya maneja la eliminación de recursos. (Y créame, escribir un destructor C ++ es mucho más fácil que escribir la cláusula final correcta de Java, o incluso el método Dispose correcto de C #).
Aún así, a veces, una
finally
cláusula sería genial. ¿Podemos hacerlo en C ++? ¡Si podemos! Y nuevamente con un uso alternativo de RAII.Conclusión: RAII es más que una filosofía en C ++: es C ++
Cuando alcanzas algún nivel de experiencia en C ++, empiezas a pensar en términos de RAII , en términos de ejecución automatizada de constructores y destructores .
Empieza a pensar en términos de alcances y los caracteres
{
y se}
convierten en los más importantes de su código.Y casi todo encaja perfectamente en términos de RAII: seguridad de excepción, mutex, conexiones de base de datos, solicitudes de base de datos, conexión de servidor, relojes, identificadores de SO, etc., y por último, pero no menos importante, memoria.
La parte de la base de datos no es despreciable, ya que, si aceptas pagar el precio, incluso puedes escribir en un estilo de " programación transaccional ", ejecutando líneas y líneas de código hasta decidir, al final, si quieres realizar todos los cambios. o, si no es posible, revertir todos los cambios (siempre que cada línea satisfaga al menos la Garantía de excepción sólida). (vea la segunda parte de este artículo de Herb's Sutter para la programación transaccional).
Y como un rompecabezas, todo encaja.
RAII es una parte tan importante de C ++ que C ++ no podría ser C ++ sin él.
Esto explica por qué los desarrolladores de C ++ experimentados están tan enamorados de RAII y por qué RAII es lo primero que buscan cuando prueban otro idioma.
Y explica por qué Garbage Collector, aunque es una magnífica pieza de tecnología en sí misma, no es tan impresionante desde el punto de vista de un desarrollador de C ++:
fuente
Por favor mira:
¿Los programadores de otros lenguajes, además de C ++, usan, conocen o entienden RAII?
RAII y punteros inteligentes en C ++
¿C ++ admite bloques 'finalmente'? (¿Y qué es este 'RAII' del que sigo escuchando?)
RAII frente a excepciones
etc ..
fuente
RAII utiliza la semántica de destructores de C ++ para administrar los recursos. Por ejemplo, considere un puntero inteligente. Tiene un constructor parametrizado del puntero que inicializa este puntero con la dirección del objeto. Asignas un puntero en la pila:
Cuando el puntero inteligente sale del alcance, el destructor de la clase de puntero elimina el objeto conectado. El puntero se asigna en la pila y el objeto en el montón.
Hay ciertos casos en los que RAII no ayuda. Por ejemplo, si usa punteros inteligentes de conteo de referencias (como boost :: shared_ptr) y crea una estructura similar a un gráfico con un ciclo, corre el riesgo de enfrentar una pérdida de memoria porque los objetos en un ciclo evitarán que se liberen entre sí. La recolección de basura ayudaría contra esto.
fuente
Me gustaría expresarlo con un poco más de fuerza que las respuestas anteriores.
RAII, Resource Acquisition Is Initialization significa que todos los recursos adquiridos deben adquirirse en el contexto de la inicialización de un objeto. Esto prohíbe la adquisición de recursos "desnuda". La razón es que la limpieza en C ++ funciona sobre la base de objetos, no sobre la base de llamadas a funciones. Por lo tanto, toda la limpieza debe realizarse mediante objetos, no llamadas a funciones. En este sentido, C ++ está más orientado a objetos que, por ejemplo, Java. La limpieza de Java se basa en llamadas a funciones en
finally
cláusulas.fuente
Estoy de acuerdo con epitis. Pero me gustaría agregar que los recursos pueden ser cualquier cosa, no solo memoria. El recurso puede ser un archivo, una sección crítica, un hilo o una conexión a una base de datos.
Se llama La adquisición de recursos es inicialización porque el recurso se adquiere cuando se construye el objeto que lo controla. Si el constructor falló (es decir, debido a una excepción), el recurso no se adquiere. Luego, una vez que el objeto sale del alcance, se libera el recurso. c ++ garantiza que todos los objetos de la pila que se hayan construido con éxito serán destruidos (esto incluye constructores de clases base y miembros incluso si falla el constructor de superclase).
La lógica detrás de RAII es hacer que la excepción de adquisición de recursos sea segura. Que todos los recursos adquiridos se liberen correctamente sin importar dónde ocurra una excepción. Sin embargo, esto depende de la calidad de la clase que adquiere el recurso (esto debe ser seguro para excepciones y esto es difícil).
fuente
El problema con la recolección de basura es que se pierde la destrucción determinista que es crucial para RAII. Una vez que una variable sale del alcance, depende del recolector de basura cuándo se reclamará el objeto. El recurso retenido por el objeto seguirá retenido hasta que se llame al destructor.
fuente
RAII proviene de Resource Allocation Is Initialization. Básicamente, significa que cuando un constructor finaliza la ejecución, el objeto construido está completamente inicializado y listo para usar. También implica que el destructor liberará cualquier recurso (por ejemplo, memoria, recursos del sistema operativo) propiedad del objeto.
En comparación con los lenguajes / tecnologías de recolección de basura (por ejemplo, Java, .NET), C ++ permite el control total de la vida de un objeto. Para un objeto asignado a la pila, sabrá cuándo se llamará al destructor del objeto (cuando la ejecución salga del alcance), cosa que no está realmente controlada en caso de recolección de basura. Incluso usando punteros inteligentes en C ++ (por ejemplo, boost :: shared_ptr), sabrá que cuando no hay ninguna referencia al objeto señalado, se llamará al destructor de ese objeto.
fuente
Cuando surge una instancia de int_buffer, debe tener un tamaño y asignará la memoria necesaria. Cuando sale del alcance, se llama a su destructor. Esto es muy útil para cosas como objetos de sincronización. Considerar
No en realidad no.
Nunca. La recolección de basura solo resuelve un subconjunto muy pequeño de la administración dinámica de recursos.
fuente
Ya hay muchas buenas respuestas aquí, pero me gustaría agregar:
Una explicación simple de RAII es que, en C ++, un objeto asignado en la pila se destruye siempre que sale de su alcance. Eso significa que se llamará a un destructor de objetos y podrá realizar toda la limpieza necesaria.
Eso significa que si un objeto se crea sin "nuevo", no es necesario "eliminar". Y esta es también la idea detrás de los "punteros inteligentes": residen en la pila y, esencialmente, envuelven un objeto basado en montón.
fuente
RAII es un acrónimo de Resource Acquisition Is Initialization.
Esta técnica es muy exclusiva de C ++ debido a su compatibilidad con Constructores y Destructores y casi automáticamente los constructores que coinciden con los argumentos que se pasan o, en el peor de los casos, se llama al constructor predeterminado y destructores si se proporciona explícitamente; de lo contrario, se llama al predeterminado. que se agrega por el compilador de C ++ se llama si no escribió un destructor explícitamente para una clase de C ++. Esto sucede solo para los objetos C ++ que se administran automáticamente, lo que significa que no están usando la tienda libre (memoria asignada / desasignada usando nuevos, nuevos [] / eliminar, eliminar [] operadores C ++).
La técnica RAII hace uso de esta función de objeto autoadministrado para manejar los objetos que se crean en el montón / tienda libre pidiendo explícitamente más memoria usando new / new [], que debe destruirse explícitamente llamando a delete / delete [] . La clase del objeto autogestionado envolverá este otro objeto que se crea en la memoria de almacenamiento dinámico / libre. Por lo tanto, cuando se ejecuta el constructor del objeto autoadministrado, el objeto empaquetado se crea en la memoria de almacenamiento dinámico / libre y cuando el identificador del objeto autogestionado sale del alcance, el destructor de ese objeto autogestionado se llama automáticamente en el que el empaquetado el objeto se destruye usando eliminar. Con los conceptos de OOP, si envuelve dichos objetos dentro de otra clase en un ámbito privado, no tendrá acceso a los métodos y miembros de las clases envueltos & esta es la razón por la que los punteros inteligentes (también conocidos como clases de control) están diseñados para. Estos punteros inteligentes exponen el objeto envuelto como un objeto escrito al mundo externo y permiten invocar cualquier miembro / método del que esté compuesto el objeto de memoria expuesto. Tenga en cuenta que los punteros inteligentes tienen varios sabores según las diferentes necesidades. Debe consultar la programación en C ++ moderno de Andrei Alexandrescu o la implementación / documentación de la biblioteca boost (www.boostorg) shared_ptr.hpp para obtener más información al respecto. Espero que esto te ayude a comprender RAII. Debe consultar la programación en C ++ moderno de Andrei Alexandrescu o la implementación / documentación de la biblioteca boost (www.boostorg) shared_ptr.hpp para obtener más información al respecto. Espero que esto te ayude a comprender RAII. Debe consultar la programación en C ++ moderno de Andrei Alexandrescu o la implementación / documentación de la biblioteca boost (www.boostorg) shared_ptr.hpp para obtener más información al respecto. Espero que esto te ayude a comprender RAII.
fuente