Comprender el significado del término y el concepto - RAII (Adquisición de recursos es inicialización)

110

¿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.

Charlie Flores
fuente
27
UTSTTC? ¡Me gusta! Es mucho más intuitivo que RAII. RAII tiene mal nombre, dudo que cualquier programador de C ++ lo cuestione. Pero no es fácil de cambiar. ;)
jalf
10
Aquí está la opinión de Stroustrup sobre el asunto: groups.google.com/group/comp.lang.c++.moderated/msg/…
sbi
3
@sbi: De todos modos, haga +1 en su comentario solo para la investigación histórica. Creo que tener el punto de vista del autor (B. Stroustrup) sobre el nombre de un concepto (RAII) es lo suficientemente interesante como para tener su propia respuesta.
paercebal
1
@paercebal: ¿Investigación histórica? Ahora me has hecho sentir muy viejo. :(Estaba leyendo todo el hilo, en ese entonces, ¡y ni siquiera me consideraba un novato en C ++!
sbi
3
+1, estaba a punto de hacer la misma pregunta, me alegro de no ser el único que entiende el concepto pero no le da sentido al nombre. Parece que debería haberse llamado RAOI - Adquisición de recursos en la inicialización.
laurent

Respuestas:

132

Entonces, ¿por qué no se llama "usar la pila para activar la limpieza" (UTSTTC :)?

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 FileHandleclase demasiado simplista que emplea RAII:

class FileHandle
{
    FILE* file;

public:

    explicit FileHandle(const char* name)
    {
        file = fopen(name);
        if (!file)
        {
            throw "MAYDAY! MAYDAY";
        }
    }

    ~FileHandle()
    {
        // The only reason we are checking the file pointer for validity
        // is because it might have been moved (see below).
        // It is NOT needed to check against a failed constructor,
        // because the destructor is NEVER executed when the constructor fails!
        if (file)
        {
            fclose(file);
        }
    }

    // The following technicalities can be skipped on the first read.
    // They are not crucial to understanding the basic idea of RAII.
    // However, if you plan to implement your own RAII classes,
    // it is absolutely essential that you read on :)



    // It does not make sense to copy a file handle,
    // hence we disallow the otherwise implicitly generated copy operations.

    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;



    // The following operations enable transfer of ownership
    // and require compiler support for rvalue references, a C++0x feature.
    // Essentially, a resource is "moved" from one object to another.

    FileHandle(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
    }

    FileHandle& operator=(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
        return *this;
    }
}

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:

void CopyFileData(FileHandle source, FileHandle dest);

void Foo()
{
    CopyFileData(FileHandle("C:\\source"), FileHandle("C:\\dest"));
}

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, Footendrí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:

class Logger
{
    FileHandle original, duplex;   // this logger can write to two files at once!

public:

    Logger(const char* filename1, const char* filename2)
    : original(filename1), duplex(filename2)
    {
        if (!filewrite_duplex(original, duplex, "New Session"))
            throw "Ugh damn!";
    }
}

El constructor de Loggerfallará si originalel constructor de 'falla (porque filename1no se pudo abrir), duplexel constructor de' falla (porque filename2no se pudo abrir), o la escritura en los archivos dentro Loggerdel cuerpo del constructor falla. En cualquiera de estos casos, no se llamará al Loggerdestructor 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.LoggeroriginalLogger

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 miembro Logger, 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 IDisposablesimplemente 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í .

Peterchen
fuente
1
+1 Por señalar que GC y ASAP no encajan. No duele a menudo, pero cuando lo hace no es fácil de diagnosticar: /
Matthieu M.
10
Una frase en particular que pasé por alto en lecturas anteriores. Dijiste que "RAII" te está diciendo: "Adquiere tus recursos dentro de los constructores". Eso tiene sentido y es casi una paráfrasis palabra por palabra de "RAII". Ahora lo entiendo aún mejor (te votaría de nuevo si pudiera :)
Charlie Flowers
2
Una ventaja importante de GC es que un marco de asignación de memoria puede evitar la creación de referencias colgantes en ausencia de código "inseguro" (si se permite el código "inseguro", por supuesto, el marco no puede evitar nada). GC también es a menudo superior a RAII cuando se trata de objetos inmutables compartidos , como cadenas, que a menudo no tienen un propietario claro y no requieren limpieza. Es lamentable que más marcos no busquen combinar GC y RAII, ya que la mayoría de las aplicaciones tendrán una combinación de objetos inmutables (donde GC sería mejor) y objetos que necesitan limpieza (donde RAII es mejor).
supercat
@supercat: Generalmente me gusta GC, pero funciona solo para recursos que GC "entiende". Por ejemplo, .NET GC no conoce el costo de los objetos COM. Cuando simplemente los crea y destruye en un bucle, felizmente permitirá que la aplicación se ejecute en el suelo con respecto al espacio de direcciones o la memoria virtual, lo que sea que ocurra primero, sin siquiera pensar en tal vez hacer un GC. --- Además, incluso en un entorno perfectamente GC'd, todavía extraño el poder de la destrucción determinista: puede aplicar el mismo patrón a otros artificats, por ejemplo, mostrando elementos de la interfaz de usuario en condiciones certianas.
peterchen
@peterchen: Una cosa que creo que está ausente en muchos pensamientos relacionados con la programación orientada a objetos es el concepto de propiedad del objeto. Hacer un seguimiento de la propiedad a menudo es claramente necesario para los objetos con recursos, pero también suele ser necesario para los objetos mutables sin recursos. En general, los objetos deben encapsular su estado mutable, ya sea en referencias a objetos inmutables posiblemente compartidos o en objetos mutables de los que sean el propietario exclusivo. Tal propiedad exclusiva no implica necesariamente acceso de escritura exclusivo, pero si lo Fooposee Bary lo Bozmuta, ...
supercat
42

Hay excelentes respuestas por ahí, así que solo agrego algunas cosas olvidadas.

0. RAII se trata de alcances

RAII se trata de ambos:

  1. adquirir un recurso (no importa qué recurso) en el constructor y deshacerlo en el destructor.
  2. tener el constructor ejecutado cuando se declara la variable, y el destructor ejecutado automáticamente cuando la variable sale del alcance.

Otros ya respondieron sobre eso, así que no daré más detalles.

1. Al codificar en Java o C #, ya usa RAII ...

JOURDAIN: ¡Qué! Cuando digo: "Nicole, tráeme mis pantuflas y dame mi gorro de dormir", ¿eso es prosa?

MAESTRO DE FILOSOFÍA: Sí, señor.

JOURDAIN: Hace más de cuarenta años que hablo en prosa sin saber nada al respecto, y le estoy muy agradecido por haberme enseñado eso.

- Molière: El caballero de la clase media, acto 2, escena 4

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 synchronizedcon lock):

void foo()
{
   // etc.

   synchronized(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

... ya está usando RAII: La adquisición de mutex se realiza en la palabra clave ( synchronizedo lock), 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 synchronizedni locken C ++, pero aún podemos tenerlos.

En C ++, estaría escrito:

void foo()
{
   // etc.

   {
      Lock lock(someObject) ; // lock is an object of type Lock whose
                              // constructor acquires a mutex on
                              // someObject and whose destructor will
                              // un-acquire it 

      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

que se puede escribir fácilmente de forma Java / C # (usando macros C ++):

void foo()
{
   // etc.

   LOCK(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

2. RAII tiene usos alternativos

CONEJO BLANCO: [cantando] Llego tarde / Llego tarde / Para una cita muy importante. / No hay tiempo para decir "Hola". / Adiós. / Llego tarde, llego tarde, llego tarde.

- Alicia en el país de las maravillas (versión Disney, 1951)

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:

void foo()
{
   double timeElapsed = 0 ;

   {
      Counter counter(timeElapsed) ;
      // do something lengthy
   }
   // now, the timeElapsed variable contain the time elapsed
   // from the Counter's declaration till the scope exit
}

que, por supuesto, se puede escribir, nuevamente, de la forma Java / C # usando una macro:

void foo()
{
   double timeElapsed = 0 ;

   COUNTER(timeElapsed)
   {
      // do something lengthy
   }
   // now, the timeElapsed variable contain the time elapsed
   // from the Counter's declaration till the scope exit
}

3. ¿Por qué falta C ++ finally?

[GRITOS] ¡Es la cuenta regresiva final !

- Europa: The Final Countdown (lo siento, me quedé sin comillas, aquí ... :-)

La finallyclá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 una returnexcepció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 finallyclá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 ++

RAII? ESTO ES C ++ !!!

- Comentario indignado del desarrollador de C ++, copiado descaradamente por un oscuro rey de Esparta y sus 300 amigos

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 ++:

  • RAII ya maneja la mayoría de los casos manejados por un GC
  • Un GC maneja mejor que RAII con referencias circulares en objetos administrados puros (mitigados por usos inteligentes de punteros débiles)
  • Aún así, un GC está limitado a la memoria, mientras que RAII puede manejar cualquier tipo de recurso.
  • Como se describió anteriormente, RAII puede hacer mucho, mucho más ...
paercebal
fuente
Un fanático de Java: Yo diría que GC es mucho más útil que RAII, ya que maneja toda la memoria y lo libera de muchos errores potenciales. Con GC, puede crear referencias circulares, devolver y almacenar referencias y es difícil equivocarse (almacenar una referencia a un objeto supuestamente de corta duración alarga su tiempo de vida, que es una especie de pérdida de memoria, pero ese es el único problema) . El manejo de recursos con GC no funciona, pero la mayoría de los recursos en una aplicación tienen un ciclo de vida trivial y los pocos restantes no son gran cosa. Ojalá pudiéramos tener tanto GC como RAII, pero eso parece imposible.
maaartinus
16

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 ..

Trigo Mitch
fuente
1
Algunos de ellos están en línea con mi pregunta, pero una búsqueda no los arrojó, ni tampoco la lista de "preguntas relacionadas" que aparece después de ingresar una nueva pregunta. Gracias por los enlaces.
Charlie Flowers
1
@Charlie: La compilación en la búsqueda es muy débil de alguna manera. Usar la sintaxis de la etiqueta ("[tema]") es muy útil, y muchas personas usan google ...
dmckee --- ex-moderator kitten
10

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:

SmartPointer pointer( new ObjectClass() );

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.

diente filoso
fuente
2
Entonces debería llamarse UCDSTMR :)
Daniel Daranas
Pensándolo bien, creo que UDSTMR es más apropiado. Se proporciona el idioma (C ++), por lo que la letra "C" no es necesaria en el acrónimo. UDSTMR son las siglas en inglés de Using Destructor Semantics To Manage Resources.
Daniel Daranas
9

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 finallycláusulas.

MSalters
fuente
Gran respuesta. Y "inicialización de un objeto" significa "constructores", ¿no?
Charlie Flowers
@Charlie: sí, especialmente en este caso.
MSalters
8

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).

iain
fuente
Excelente, gracias por explicar la razón de ser del nombre. Según tengo entendido, podría parafrasear RAII como, "Nunca adquiera ningún recurso a través de ningún otro mecanismo que no sea la inicialización (basada en el constructor)". ¿Si?
Charlie Flowers
Sí, esta es mi política, sin embargo, desconfío mucho de escribir mis propias clases de RAII ya que deben ser excepcionales. Cuando los escribo, trato de garantizar la seguridad de las excepciones reutilizando otras clases RAII escritas por expertos.
iain
No los he encontrado difíciles de escribir. Si sus clases son lo suficientemente pequeñas, no son difíciles en absoluto.
Rob K
7

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.

Mark Ransom
fuente
4
El problema no es solo el determinismo. El problema real es que los finalizadores (nombres de Java) se interponen en el camino de GC. GC es eficiente porque no recuerda los objetos muertos, sino que los ignora en el olvido. Los GC deben rastrear objetos con finalizadores de una manera diferente para garantizar que se llamen
David Rodríguez - dribeas
1
excepto en java / c # probablemente limpiaría en un bloque final en lugar de en un finalizador.
jk.
4

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.

Cătălin Pitiș
fuente
3

¿Y cómo puedes hacer algo en la pila que provoque la limpieza de algo que vive en el montón?

class int_buffer
{
   size_t m_size;
   int *  m_buf;

   public:
   int_buffer( size_t size )
     : m_size( size ), m_buf( 0 )
   {
       if( m_size > 0 )
           m_buf = new int[m_size]; // will throw on failure by default
   }
   ~int_buffer()
   {
       delete[] m_buf;
   }
   /* ...rest of class implementation...*/

};


void foo() 
{
    int_buffer ib(20); // creates a buffer of 20 bytes
    std::cout << ib.size() << std::endl;
} // here the destructor is called automatically even if an exception is thrown and the memory ib held is freed.

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

class mutex
{
   // ...
   take();
   release();

   class mutex::sentry
   {
      mutex & mm;
      public:
      sentry( mutex & m ) : mm(m) 
      {
          mm.take();
      }
      ~sentry()
      {
          mm.release();
      }
   }; // mutex::sentry;
};
mutex m;

int getSomeValue()
{
    mutex::sentry ms( m ); // blocks here until the mutex is taken
    return 0;  
} // the mutex is released in the destructor call here.

Además, ¿hay casos en los que no puede usar RAII?

No en realidad no.

¿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?

Nunca. La recolección de basura solo resuelve un subconjunto muy pequeño de la administración dinámica de recursos.

Rob K
fuente
He usado Java y C # muy poco, por lo que nunca me lo he perdido, pero GC ciertamente restringió mi estilo en lo que respecta a la administración de recursos cuando tuve que usarlos, porque no podía usar RAII.
Rob K
1
He usado mucho C # y estoy de acuerdo contigo al 100%. De hecho, considero que un GC no determinista es una desventaja en un idioma.
Nemanja Trifunovic
2

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.

E Dominique
fuente
1
No, no lo hacen. Pero, ¿tiene una buena razón para crear un puntero inteligente en el montón? Por cierto, el puntero inteligente fue solo un ejemplo de dónde RAII puede ser útil.
E Dominique
1
Quizás mi uso de "pila" vs "montón" es un poco descuidado - por un objeto en "la pila" me refiero a cualquier objeto local. Naturalmente, puede ser parte de un objeto, por ejemplo, en el montón. Por "crear un puntero inteligente en el montón", quise usar nuevo / eliminar en el puntero inteligente.
E Dominique
1

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.

tecnólogo
fuente