¿Qué se entiende por adquisición de recursos es inicialización (RAII)?
¿Qué se entiende por adquisición de recursos es inicialización (RAII)?
Juan
el_mandril
Es un nombre realmente terrible para un concepto increíblemente poderoso, y quizás una de las cosas principales que los desarrolladores de C++ pierden cuando cambian a otros lenguajes. Ha habido un poco de movimiento para tratar de cambiar el nombre de este concepto como Gestión de recursos limitada al alcanceaunque no parece haberse puesto de moda todavía.
Cuando decimos ‘Recurso’ no solo nos referimos a la memoria: podrían ser identificadores de archivos, sockets de red, identificadores de bases de datos, objetos GDI… En resumen, cosas de las que tenemos un suministro finito y por lo que debemos poder controlar su uso. El aspecto ‘limitado al alcance’ significa que la vida útil del objeto está vinculada al alcance de una variable, por lo que cuando la variable sale del alcance, el destructor liberará el recurso. Una propiedad muy útil de esto es que proporciona una mayor seguridad de excepción. Por ejemplo, compare esto:
RawResourceHandle* handle=createNewResource();
handle->performInvalidOperation(); // Oops, throws exception
...
deleteResource(handle); // oh dear, never gets called so the resource leaks
Con el RAII
class ManagedResourceHandle {
public:
ManagedResourceHandle(RawResourceHandle* rawHandle_) : rawHandle(rawHandle_) {};
~ManagedResourceHandle() {delete rawHandle; }
... // omitted operator*, etc
private:
RawResourceHandle* rawHandle;
};
ManagedResourceHandle handle(createNewResource());
handle->performInvalidOperation();
En este último caso, cuando se lanza la excepción y se deshace la pila, las variables locales se destruyen, lo que garantiza que nuestro recurso se limpie y no se filtre.
-
@the_mandrill: probé ideone.com/1Jjzuc este programa. Pero no hay llamada de destructor. El tomdalling.com/blog/software-design/… dice que C++ garantiza que se llamará al destructor de objetos en la pila, incluso si se lanza una excepción. Entonces, ¿por qué destructor no se ejecutó aquí? ¿Mi recurso se filtró o nunca se liberará o liberará?
– Destructor
20 de agosto de 2015 a las 16:40
-
Se lanza una excepción, pero no la detecta, por lo que la aplicación finaliza. Si ajusta con un intento { } catch () {} entonces funciona como se esperaba: ideone.com/xm2GR9
– el_mandril
21 de agosto de 2015 a las 10:41
-
No estoy muy seguro si
Scope-Bound
es la mejor opción de nombre aquí ya que los especificadores de clase de almacenamiento Juntos con el alcance determina la duración del almacenamiento de una entidad. Estrecharlo hecho al límite del alcance es quizás una simplificación útil, sin embargo, no es 100% preciso– Haz click en mi
12 de abril de 2018 a las 8:53
-
pero como explicas
RA is initialization
en el nombre original? Lo que entiendo por RAII es que es responsabilidad de cada objeto encargarse de su eliminación una vez fuera del alcance. para mi no coincide conis initialization
porque todo está relacionado con el destructor. Todavía estoy confundido por ese nombre idiomático.– ahora buey
11 de noviembre de 2020 a las 12:49
-
“… cosas número 1 que los desarrolladores de C++ pierden…” ¿No es esto similar a probar con recursos en Java? Parece resolver el mismo problema y no veo ninguna ventaja o desventaja de RAII en comparación con la solución de Java.
– marc.guenther
17 de diciembre de 2021 a las 15:58
Péter Török
Este es un lenguaje de programación que significa brevemente que usted
- encapsular un recurso en una clase (cuyo constructor generalmente, pero no necesariamente **, adquiere el recurso y su destructor siempre lo libera)
- usar el recurso a través de una instancia local de la clase*
- el recurso se libera automáticamente cuando el objeto queda fuera del alcance
Esto garantiza que pase lo que pase mientras el recurso está en uso, eventualmente se liberará (ya sea debido a un retorno normal, destrucción del objeto contenedor o lanzamiento de una excepción).
Es una buena práctica ampliamente utilizada en C++ porque, además de ser una forma segura de manejar los recursos, también hace que su código sea mucho más limpio, ya que no necesita mezclar el código de manejo de errores con la funcionalidad principal.
*
Actualizar: “local” puede significar una variable local o una variable miembro no estática de una clase. En el último caso, la variable miembro se inicializa y destruye con su objeto propietario.
**
Actualización2: como señaló @sbi, el recurso, aunque a menudo se asigna dentro del constructor, también se puede asignar fuera y pasar como un parámetro.
-
AFAIK, el acrónimo no implica que el objeto tenga que estar en una variable local (pila). Podría ser una variable miembro de otro objeto, por lo que cuando se destruye el objeto ‘retenedor’, el objeto miembro también se destruye y el recurso se libera. De hecho, creo que el acrónimo significa específicamente que no hay
open()
/close()
métodos para inicializar y liberar el recurso, solo el constructor y el destructor, por lo que la ‘retención’ del recurso es solo el tiempo de vida del objeto, sin importar si ese tiempo de vida es manejado por el contexto (pila) o explícitamente (asignación dinámica)– Javier
23 de febrero de 2010 a las 20:51
-
En realidad, nada dice que el recurso debe adquirirse en el constructor. Los flujos de archivos, las cadenas y otros contenedores hacen eso, pero el recurso también podría ser aprobado al constructor, como suele ser el caso con los punteros inteligentes. Dado que la suya es la respuesta más votada, es posible que desee solucionar esto.
– sbi
8 oct 2012 a las 20:59
-
No es un acrónimo, es una abreviatura. La mayoría de la gente del IIRC lo pronuncia “ar ey ay ay”, por lo que en realidad no califica para un acrónimo como say DARPA, que se pronuncia DARPA en lugar de deletrearse. Además, diría que RAII es un paradigma en lugar de un mero idioma.
– dtech
5 de agosto de 2013 a las 10:24
-
@Peter Torok: lo intenté ideone.com/1Jjzuc este programa. Pero no hay llamada de destructor. los tomdalling.com/blog/software-design/… dice que C++ garantiza que se llamará al destructor de objetos en la pila, incluso si se lanza una excepción. Entonces, ¿por qué destructor no se ejecutó aquí? ¿Mi recurso se filtró o nunca se liberará o liberará?
– Destructor
20 de agosto de 2015 a las 16:35
-
En ese ejemplo, la excepción no se detecta, por lo que el programa finaliza instantáneamente. Si detecta la excepción, se llama al destructor cuando se desenrolla la pila.
– el_mandril
12 de noviembre de 2020 a las 16:36
sbi
“RAII” significa “Adquisición de recursos es inicialización” y en realidad es un nombre bastante inapropiado, ya que no es un recurso adquisición (y la inicialización de un objeto) que le concierne, pero liberando el recurso (mediante destrucción de un objeto).
Pero RAII es el nombre que obtuvimos y se mantiene.
En esencia, el modismo presenta recursos encapsulados (trozos de memoria, archivos abiertos, mutexes desbloqueados, lo que sea) en objetos automáticos localesy hacer que el destructor de ese objeto libere el recurso cuando el objeto se destruye al final del ámbito al que pertenece:
{
raii obj(acquire_resource());
// ...
} // obj's dtor will call release_resource()
Por supuesto, los objetos no siempre son objetos automáticos locales. También podrían ser miembros de una clase:
class something {
private:
raii obj_; // will live and die with instances of the class
// ...
};
Si dichos objetos administran la memoria, a menudo se los denomina “punteros inteligentes”.
Hay muchas variaciones de esto. Por ejemplo, en los primeros fragmentos de código surge la pregunta de qué pasaría si alguien quisiera copiar obj
. La salida más fácil sería simplemente prohibir la copia. std::unique_ptr<>
un puntero inteligente para ser parte de la biblioteca estándar como se presenta en el próximo estándar de C++, hace esto.
Otro puntero tan inteligente, std::shared_ptr
presenta “propiedad compartida” del recurso (un objeto asignado dinámicamente) que contiene. Es decir, se puede copiar libremente y todas las copias se refieren al mismo objeto. El puntero inteligente realiza un seguimiento de cuántas copias se refieren al mismo objeto y lo eliminará cuando se destruya la última.
Una tercera variante es presentada por std::auto_ptr
que implementa una especie de semántica de movimiento: un objeto es propiedad de un solo puntero, e intentar copiar un objeto dará como resultado (a través de la piratería de sintaxis) la transferencia de propiedad del objeto al objetivo de la operación de copia.
-
std::auto_ptr
es una versión obsoleta destd::unique_ptr
.std::auto_ptr
tipo de semántica de movimiento simulado tanto como fue posible en C ++ 98,std::unique_ptr
utiliza la nueva semántica de movimiento de C++11. Se creó una nueva clase porque la semántica de movimiento de C++ 11 es más explícita (requierestd::move
excepto de temporal) mientras que estaba predeterminado para cualquier copia de non-const enstd::auto_ptr
.– Jan Hudec
5 de agosto de 2013 a las 9:40
-
@JiahaoCai: Una vez, hace muchos años (en Usenet), el propio Stroustrup lo dijo.
– sbi
20 de enero de 2020 a las 18:49
La vida útil de un objeto está determinada por su alcance. Sin embargo, a veces necesitamos, o es útil, crear un objeto que viva independientemente del ámbito donde fue creado. En C++, el operador new
se utiliza para crear tal objeto. Y para destruir el objeto, el operador delete
puede ser usado. Objetos creados por el operador new
se asignan dinámicamente, es decir, se asignan en memoria dinámica (también llamada montón o tienda gratis). Entonces, un objeto que fue creado por new
continuará existiendo hasta que se destruya explícitamente usando delete
.
Algunos errores que pueden ocurrir al usar new
y delete
están:
- objeto filtrado (o memoria): usando
new
asignar un objeto y olvidarse dedelete
el objeto. - Eliminación prematura (o referencia colgante): sosteniendo otro puntero a un objeto,
delete
el objeto, y luego use el otro puntero. - Eliminación doble: tratando de
delete
un objeto dos veces.
En general, se prefieren las variables con ámbito. Sin embargo, RAII se puede utilizar como una alternativa a new
y delete
hacer que un objeto viva independientemente de su alcance. Esta técnica consiste en llevar el puntero al objeto que se asignó en el montón y colocarlo en un objeto manejador/administrador. Este último tiene un destructor que se encargará de destruir el objeto. Esto garantizará que el objeto esté disponible para cualquier función que quiera acceder a él, y que el objeto se destruya cuando finalice la vida útil del manejar objeto finaliza, sin necesidad de una limpieza explícita.
Los ejemplos de la biblioteca estándar de C++ que usan RAII son std::string
y std::vector
.
Considere esta pieza de código:
void fn(const std::string& str)
{
std::vector<char> vec;
for (auto c : str)
vec.push_back(c);
// do something
}
cuando crea un vector y le empuja elementos, no le importa asignar y desasignar dichos elementos. El vector utiliza new
para asignar espacio para sus elementos en el montón, y delete
para liberar ese espacio. Usted, como usuario de vector, no se preocupa por los detalles de implementación y confiará en que vector no se filtre. En este caso, el vector es el manejar objeto de sus elementos
Otros ejemplos de la biblioteca estándar que usan RAII son std::shared_ptr
, std::unique_ptr
y std::lock_guard
.
Otro nombre para esta técnica es SBRMcorto para Gestión de recursos limitada al alcance.
dennis
El libro Programación en C++ con patrones de diseño revelados describe RAII como:
- Adquirir todos los recursos
- Uso de recursos
- Liberando recursos
Donde
-
Los recursos se implementan como clases, y todos los punteros tienen contenedores de clase a su alrededor (lo que los convierte en punteros inteligentes).
-
Los recursos se adquieren invocando a sus constructores y se liberan implícitamente (en orden inverso al de adquisición) invocando a sus destructores.
-
@Brandin Edité mi publicación para que los lectores se concentren en el contenido que importa, en lugar de debatir el área gris de la ley de derechos de autor de lo que constituye un uso legítimo.
-Dennis
29 mayo 2016 a las 13:33
mohamed moridi
Hay tres partes en una clase RAII:
- Se renuncia al recurso en el destructor.
- Las instancias de la clase se asignan en la pila
- El recurso se adquiere en el constructor. Esta parte es opcional, pero común.
RAII significa “Adquisición de recursos es inicialización”. La parte de “adquisición de recursos” de RAII es donde comienza algo que debe finalizar más tarde, como:
- Abriendo un archivo
- Asignando algo de memoria
- Adquirir un candado
La parte “es inicialización” significa que la adquisición ocurre dentro del constructor de una clase.
-
@Brandin Edité mi publicación para que los lectores se concentren en el contenido que importa, en lugar de debatir el área gris de la ley de derechos de autor de lo que constituye un uso justo.
-Dennis
29 mayo 2016 a las 13:33
Dmitri Pavlov
La gestión manual de la memoria es una pesadilla que los programadores han estado inventando formas de evitar desde la invención del compilador. Los lenguajes de programación con recolectores de basura hacen la vida más fácil, pero a costa del rendimiento. En este articulo – Eliminando al recolector de basura: The RAII Wayel ingeniero de Toptal Peter Goodspeed-Niklaus nos da un vistazo a la historia de los recolectores de basura y explica cómo las nociones de propiedad y préstamo pueden ayudar a eliminar a los recolectores de basura sin comprometer sus garantías de seguridad.
en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization
– usuario151323
23 de febrero de 2010 a las 20:39
Esto es lo que lo lleva a casa para mí. stroustrup.com/bs_faq2.html#finalmente
– Hal Canarias
21 mayo 2013 a las 22:45
¡Referencia de Microsoft con 3 oraciones y 2 ejemplos pero muy clara! msdn.microsoft.com/en-us/library/hh438480.aspx
– Gab是好人
17/11/2016 a las 21:58