¿Qué se entiende por adquisición de recursos es inicialización (RAII)?
¿Qué se entiende por adquisición de recursos es inicialización (RAII)?
Respuestas:
Es un nombre realmente terrible para un concepto increíblemente poderoso, y quizás una de las cosas número 1 que los desarrolladores de C ++ pierden cuando cambian a otros idiomas. Ha habido un poco de movimiento para tratar de cambiar el nombre de este concepto como Gestión de recursos limitada por alcance , aunque todavía no parece haberse dado cuenta.
Cuando decimos 'Recurso' no solo nos referimos a la memoria: podrían ser identificadores de archivos, zócalos de red, identificadores de bases de datos, objetos GDI ... En resumen, cosas de las que tenemos un suministro finito y, por lo tanto, debemos ser capaces de controlar su uso. El aspecto "vinculado al alcance" significa que la vida útil del objeto está vinculada al alcance de una variable, por lo que cuando la variable se sale del alcance, el destructor liberará el recurso. Una propiedad muy útil de esto es que ofrece una mayor seguridad de excepción. Por ejemplo, compara 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 la pila se desenrolla, las variables locales se destruyen, lo que garantiza que nuestro recurso se limpie y no se filtre.
Scope-Bound
es la mejor opción de nombre aquí, ya que los especificadores de clase de almacenamiento junto con el alcance determinan la duración de almacenamiento de una entidad. Reducirlo a un límite de alcance puede ser una simplificación útil, sin embargo, no es 100% preciso
Este es un lenguaje de programación que significa brevemente que usted
Esto garantiza que pase lo que pase mientras el recurso está en uso, eventualmente se liberará (ya sea debido a un retorno normal, la destrucción del objeto que lo contiene o una excepción lanzada).
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.
*
Actualización: "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 se 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.
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 la vida útil del objeto, sin importar si esa vida es manejado por el contexto (pila) o explícitamente (asignación dinámica)
"RAII" significa "Adquisición de recursos es inicialización" y en realidad es un nombre poco apropiado, ya que no se trata de la adquisición de recursos (y la inicialización de un objeto), sino de liberar el recurso (mediante la destrucción de un objeto )
Pero RAII es el nombre que tenemos y se queda.
En esencia, el modismo presenta recursos de encapsulación (fragmentos de memoria, archivos abiertos, mutexes desbloqueados, lo que sea) en objetos locales y automáticos , y tiene el destructor de ese objeto que libera el recurso cuando el objeto se destruye en el fin del alcance al que pertenece:
{
raii obj(acquire_resource());
// ...
} // obj's dtor will call release_resource()
Por supuesto, los objetos no siempre son objetos locales, automáticos. También podrían ser miembros de una clase:
class something {
private:
raii obj_; // will live and die with instances of the class
// ...
};
Si tales objetos manejan la memoria, a menudo se los llama "punteros inteligentes".
Hay muchas variaciones de esto. Por ejemplo, en los primeros fragmentos de código surge la pregunta de qué sucedería si alguien quisiera copiar obj
. La salida más fácil sería simplemente no permitir 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 C ++, hace esto.
Otro puntero inteligente de este tipo, std::shared_ptr
presenta la "propiedad compartida" del recurso (un objeto asignado dinámicamente) que posee. 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 porstd::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 (mediante piratería de sintaxis) la transferencia de la propiedad del objeto al objetivo de la operación de copia.
std::auto_ptr
es la versión obsoleta de std::unique_ptr
. std::auto_ptr
tipo de semántica de movimiento simulada 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 (requiere std::move
excepto de temporal) mientras que se omitió cualquier copia de no const in std::auto_ptr
.
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 alcance donde fue creado. En C ++, el operador new
se utiliza para crear dicho objeto. Y para destruir el objeto, delete
se puede usar el operador . Los objetos creados por el operador new
se asignan dinámicamente, es decir, se asignan en memoria dinámica (también llamada almacenamiento dinámico o almacenamiento gratuito ). Por lo tanto, un objeto creado por new
continuará existiendo hasta que se destruya explícitamente usando delete
.
Algunos errores que pueden ocurrir al usar new
y delete
son:
new
para asignar un objeto y olvidarlo delete
.delete
el objeto, y luego usar el otro puntero.delete
un objeto dos veces.En general, se prefieren las variables de ámbito. Sin embargo, RAII puede usarse como una alternativa new
y delete
hacer que un objeto viva independientemente de su alcance. Dicha técnica consiste en llevar el puntero al objeto que se asignó en el montón y colocarlo en un objeto controlador / 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 desee acceder a él, y que el objeto se destruya cuando la vida útil del objeto de manejo finalice , sin la necesidad de una limpieza explícita.
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 creas un vector y empujas elementos a él, no te 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. Como usuario del vector, no le importan los detalles de implementación y confiará en que el vector no se filtre. En este caso, el vector es el objeto manipulador de de sus elementos.
Otros ejemplos de la biblioteca estándar que RAII uso son std::shared_ptr
, std::unique_ptr
y std::lock_guard
.
Otro nombre para esta técnica es SBRM , abreviatura de Scope-Bound Resource Management .
El libro C ++ Programming with Design Patterns Revealed describe RAII como:
Dónde
Los recursos se implementan como clases, y todos los punteros tienen envoltorios de clase a su alrededor (haciéndolos punteros inteligentes).
Los recursos se adquieren invocando a sus constructores y se liberan implícitamente (en orden inverso a la adquisición) invocando a sus destructores.
Hay tres partes en una clase RAII:
RAII significa "Adquisición de recursos es inicialización". La parte de "adquisición de recursos" de RAII es donde comienza algo que debe terminar más tarde, como:
La parte "es inicialización" significa que la adquisición ocurre dentro del constructor de una clase.
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 facilitan la vida, pero a costa del rendimiento. En este artículo: Eliminando el recolector de basura: a la manera RAII , el 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 los recolectores de basura sin comprometer sus garantías de seguridad.