Pregunta

Podría desarrolladores C ++ por favor nos da una buena descripción de lo que RAII es, por qué es importante, y si es o no podría tener alguna relación con otras lenguas?

DO saber un poco. Creo que es sinónimo de "Raii". Sin embargo, ese nombre no jive con mi entendimiento (posiblemente incorrecta) de lo que se RAII: Me da la impresión de que RAII es un modo de inicialización de objetos en la pila de tal manera que, cuando esas variables salir de su ámbito, los destructores de forma automática lo hará se llamará haciendo que los recursos para ser limpiados.

¿Por qué no es la llamada "por medio de la pila para activar la limpieza" (UTSTTC :)? ¿Cómo se obtiene a partir de ahí "RAII"?

Y cómo se puede hacer algo en la pila que hará que la limpieza de algo que vive en el montón? Además, hay casos en los que no se puede utilizar RAII? ¿Alguna vez se encuentra deseando para la recolección de basura? Al menos se logró un recolector de basura se puede utilizar para algunos objetos mientras que deja otras?

Gracias.

¿Fue útil?

Solución

  

¿Por qué es que no se llama "el uso de la pila para activar la limpieza" (UTSTTC:)?

RAII le está diciendo qué hacer: Adquirir su recurso en un constructor! Yo añadiría: un recurso, un constructor. UTSTTC es sólo una aplicación de la que, RAII es mucho más.

Gestión de Recursos chupa. Aquí, recurso es cualquier cosa que necesita la limpieza después de su uso. Estudios de proyectos a través de muchas plataformas muestran que la mayoría de los insectos están relacionados con la gestión de recursos - y es especialmente mala en Windows (debido a los muchos tipos de objetos y asignadores)

.

En C ++, gestión de recursos es particularmente complicado debido a la combinación de las excepciones y plantillas (++ estilo C). Para un vistazo bajo el capó, véase GOTW8 ).


C ++ garantiza que se llama al destructor si y sólo si el constructor tuvieron éxito. Basándose en eso, RAII puede resolver muchos problemas desagradables del programador medio podría incluso no ser conscientes de. Aquí hay algunos ejemplos más allá de la "mis variables locales serán destruidos cuando vuelva".

Vamos a empezar con una clase que emplea RAII FileHandle demasiado simplista:

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 no (con una excepción), ninguna otra función miembro - ni siquiera el destructor - se llama.

RAII evita el uso de objetos en un estado no válido. que ya hace la vida más fácil antes de que incluso utilizamos el objeto.

Ahora, vamos a echar 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 a manejado: ningún archivo se puede abrir, sólo un archivo se puede abrir, ambos archivos se pueden abrir, pero no pudieron copiar los archivos. En una implementación no RAII, Foo tendría que manejar los tres casos explícitamente.

RAII que fueron adquiridas, incluso cuando se adquieren múltiples recursos dentro de un estado de cuenta.

Ahora, vamos a agregamos 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 Logger fallará si el constructor de original falla (porque filename1 no se pudo abrir), el constructor de duplex falla (porque filename2 no se pudo abrir), o escribiendo a los archivos dentro del cuerpo del constructor Logger falla. En cualquiera de estos casos, el destructor de Logger será no se llama - por lo que no puede confiar en destructor de Logger para liberar los archivos. Pero si original fue construido, su destructor será llamado durante la limpieza del constructor Logger.

RAII simplifica la limpieza después de la construcción parcial.


Los puntos negativos:

Los puntos negativos? Todos los problemas se pueden resolver con RAII y punteros inteligentes ;-)

RAII es a veces difícil de manejar cuando se necesita la adquisición retardada, empujar objetos agregados en el montón.
Imagínese el registrador necesita un SetTargetFile(const char* target). En ese caso, el mango, que todavía tiene que ser un miembro de Logger, tiene que residir en el montón (por ejemplo en un puntero inteligente, para desencadenar la destrucción de la empuñadura apropiada.)

Nunca he deseado recolección de basura en realidad. Cuando hago C # a veces siento un momento de felicidad que simplemente no necesito a la atención, pero mucho más echo de menos todos los juguetes divertidos que se pueden crear a través de la destrucción determinista. (Usando IDisposable simplemente no es suficiente.)

He tenido una estructura particularmente compleja que podrían haber beneficiado de GC, donde punteros inteligentes "simples" causarían referencias circulares a través de múltiples clases. Nos ir tirando en equilibrando cuidadosamente punteros fuertes y débiles, pero en cualquier momento que queremos cambiar algo, tenemos que estudiar una gran tabla de relaciones. GC podría haber sido mejor, pero algunos de los componentes de los recursos que deben ser Liberar retenidaLo antes posible.


Una nota sobre la muestra FileHandle: No se pretende ser completa, sólo una muestra - pero resultó incorrecta. Gracias Johannes Schaub para señalar y FredOverflow para convertirla en una correcta C ++ 0x solución. Con el tiempo, me he adaptado a las href="http://www.codeproject.com/KB/stl/boostsp_handleref.aspx" enfoque documentado aquí .

Otros consejos

Hay excelentes respuestas por ahí, por lo que sólo añadir algunas cosas olvidadas.

0. RAII es acerca de los ámbitos

RAII se trata tanto:

  1. adquisición de un recurso (no importa qué recursos) en el constructor, y poco adquirirla en el destructor.
  2. tener el constructor ejecuta cuando se declara la variable, y el destructor ejecuta automáticamente cuando la variable sale del ámbito.

Otros ya respondidas por eso, por lo que no voy a dar más detalles.

1. Cuando la codificación en Java o C #, ya utiliza RAII ...

  

que Monsieur Jourdain: Qué! Cuando digo, "Nicole, tráeme mis zapatillas,   y dame gorro de dormir," que es la prosa?

     

Filosofía MAESTRO: Sí., Sir

     

que Monsieur Jourdain: Desde hace más de cuarenta años he estado hablando en prosa sin saber nada de él, y estoy muy agradecido por haberme enseñado que

.      

- Molière: El caballero de la clase media, Acto 2, Escena 4

Como Monsieur Jourdain hizo con prosa, C # y Java incluso las personas ya usan RAII, pero en formas ocultas. Por ejemplo, el siguiente código de Java (que se escribe de la misma manera en C # mediante la sustitución de synchronized con lock):

void foo()
{
   // etc.

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

   // etc.
}

... ya está utilizando RAII:. La adquisición de exclusión mutua se realiza en la palabra clave (o synchronized lock), y la desclasificación de adquisición se hará cuando se sale del alcance

Es tan natural en su notación prácticamente no requiere explicación, incluso para las personas que nunca se enteraron de RAII.

La ventaja C ++ tiene más de Java y C # aquí es que nada se puede hacer usando RAII. Por ejemplo, no hay acumulación en el equivalente directo del synchronized ni lock en C ++, pero aún pueden tener ellos.

En C ++, que se escribiría:

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 puede ser fácilmente escrito del / C # forma de Java (usando macros C ++):

void foo()
{
   // etc.

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

   // etc.
}

2. RAII tienen usos alternativos

  

CONEJO BLANCO: [cantar] llego tarde / llego tarde / Para una fecha 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 de Disney de 1951)

Usted sabe cuando el constructor se llama (en la declaración de objeto), y saber cuándo se llamará su destructor correspondiente (en la salida del alcance), por lo que puede escribir código con casi mágico sino una línea. Bienvenido al país de las maravillas C ++ (al menos, desde un punto de vista de C ++ desarrollador).

Por ejemplo, se puede escribir un objeto de contador (dejo como ejercicio) y lo usa tal declarando su variable, al igual que el objeto de bloqueo anteriormente se utilizó:

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, de nuevo, el Java / C # forma utilizando 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é C ++ carecer finally?

  

[SHOUTING] Es la final cuenta regresiva!

     

- Europa: The Final Countdown (lo siento, yo estaba fuera de cotizaciones, aquí ...: -)

La cláusula finally se utiliza en C # / Java para manejar la eliminación de recursos en caso de salida de alcance (ya sea a través de un return o una excepción lanzada).

lectores de especificación astuto se habrá dado cuenta de C ++ no tiene ninguna cláusula finally. Y esto no es un error, debido a que C ++ no lo necesita, como RAII ya manejar la disposición de recursos. (Y créanme, escrito en C ++ es destructor magnitudes más fácil que escribir el derecho de Java, finalmente, cláusula, o incluso un método Dispose correcta C # 's).

Sin embargo, a veces, una cláusula finally estaría bien. Podemos hacerlo en C ++? Sí, se puede! Y de nuevo con un uso alternativo de RAII.

Conclusión: RAII es una más de la filosofía en C ++: Es C ++

  

RAII? ESTO ES C ++ !!!

     

- C ++ de indignados desarrolladorcomentario, descaradamente copiado por un rey Sparta oscura y sus 300 amigos

Al llegar a cierto nivel de experiencia en C ++, se empieza a pensar en términos de RAII , en términos de ejecución construtors y destructores automatizados .

Se empieza a pensar en términos de alcances , y los personajes { y } se convierten en unos de los más importantes en el código.

Y casi todo encaja a la perfección en términos de seguridad: RAII excepción, exclusiones mutuas, las conexiones de bases de datos, las solicitudes de base de datos, conexión de servidor, relojes, identificadores de sistema operativo, etc., y por último, pero no menos importante, la memoria

.

La parte de base de datos no es despreciable, ya que, si se acepta que pagar el precio, incluso se puede escribir en un " programación transaccional " estilo, la ejecución de líneas y líneas de código hasta que decidir, en el final, si quieres cometer todos los cambios, o, si no es posible, teniendo todos los cambios invirtió de nuevo (siempre y cuando cada línea de satisfacer al menos la garantía de Excepción fuerte). (Véase la segunda parte de este artículo de Herb Sutter para la programación transaccional).

Y como un rompecabezas, todo encaja.

RAII es tanto parte de C ++, C ++ no podría ser C ++ sin él.

Esto explica por qué los desarrolladores con experiencia en C ++ son tan enamorado de RAII, y por qué RAII es la primera cosa que la búsqueda cuando se trata de otro idioma.

Y explica por qué el recolector de basura, mientras que una pieza magnífica de la tecnología en sí misma, no es tan impresionante desde el punto de vista de un desarrollador de C ++:

  • RAII maneja ya la mayoría de los casos manejados por un GC
  • Un GC ofertas mejor que RAII con referencias circulares en los objetos gestionados puros (mitigado por usos inteligentes de punteros débiles)
  • Todavía un GC se limita a la memoria, mientras que RAII puede manejar cualquier tipo de recurso.
  • Como se describió anteriormente, RAII puede hacer mucho, mucho más ...

RAII está usando C ++ semántica destructores para gestionar los recursos. Por ejemplo, considere un puntero inteligente. Usted tiene un constructor con parámetros del puntero que inicializa este puntero con la dirección del objeto. Se asigna un puntero de pila:

SmartPointer pointer( new ObjectClass() );

Cuando el puntero inteligente se sale del ámbito del destructor de la clase puntero elimina el objeto conectado. El puntero se asigna-apilar y el objeto -. Heap-asignado

Hay ciertos casos cuando RAII no ayuda. Por ejemplo, si utiliza punteros inteligentes de conteo de referencias (como impulso :: shared_ptr) y crear una estructura gráfica similar con un ciclo corre el riesgo frente a una pérdida de memoria debido a que los objetos en un ciclo evitarán unos a otros de ser liberado. La recolección de basura ayudaría en contra de este.

Estoy de acuerdo con cpitis. Pero me gustaría añadir que los recursos no pueden ser cualquier cosa con tal de memoria. El recurso puede ser un archivo, una sección crítica, un hilo o una conexión de base de datos.

Se llama Raii porque el recurso se adquiere cuando se crea el objeto de controlar el recurso, si el constructor no (es decir, debido a una excepción) el recurso no se adquiere. A continuación, una vez que el objeto sale del ámbito se libera el recurso. c ++ garantiza que todos los objetos en la pila que se han construido con éxito será destruido (esto incluye constructores de clases base y miembros incluso si el constructor de superclase falla).

Lo racional detrás RAII es hacer que los recursos excepción adquisición segura. Que todos los recursos adquiridos se liberan correctamente, no importa donde se produce una excepción. Sin embargo, esto depende de la calidad de la clase que adquiere el recurso (esto debe ser una excepción seguro y esto es difícil).

Me gustaría poner un poco más fuerte a continuación, las respuestas anteriores.

RAII, Recursos de adquisición es de inicialización significa que todos los recursos adquiridos deben ser adquiridos en el contexto de la inicialización de un objeto. Esta prohíbe la adquisición de recursos "desnudo". La razón es que la limpieza en C ++ funciona en régimen de objeto, no funciona de guardia base. Por lo tanto, toda la limpieza debe hacerse por los objetos, no se llama a la función. En este sentido C ++ es más orientadas a objeto a continuación, por ejemplo, Java. la limpieza de Java se basa en las llamadas a funciones en cláusulas finally.

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 se sale del ámbito, que es hasta el recolector de basura cuando se recuperó el objeto. El recurso que se mantiene por el objeto continuará a realizarse hasta que el destructor se llama.

RAII viene de asignación de recursos es la inicialización. Básicamente, esto significa que cuando un constructor termina la ejecución, el objeto construido es inicializado completamente y listo para usar. También implica que el destructor se libere a todos los recursos de memoria (por ejemplo, los recursos del sistema operativo) de propiedad del objeto.

En comparación con los lenguajes de basura recogida / tecnologías (por ejemplo, Java, .NET), C ++ permite un control total de la vida de un objeto. Para un objeto de pila asignado, usted sabrá cuándo se llamará al destructor del objeto (cuando la ejecución se pone fuera del alcance), cosa que no está muy controlado en el caso de la recolección de basura. Incluso el uso de punteros inteligentes en C ++ (por ejemplo, impulso :: shared_ptr), sabrá que cuando no hay una referencia al objeto puntiagudo, el destructor de ese objeto será llamado.

  

Y cómo se puede hacer algo en la pila que hará que 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 una instancia de int_buffer llega a existir debe tener un tamaño, y se asignará la memoria necesaria. Cuando se sale del ámbito, es destructor se llama. Esto es muy útil para cosas como objetos de sincronización. Considere

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 se puede utilizar RAII?

No, no realmente.

  

¿Alguna vez se encuentra deseando para la recolección de basura? Al menos se logró un recolector de basura se puede utilizar para algunos objetos mientras que deja otras?

Nunca. La recolección de basura sólo resuelve un subconjunto muy pequeño de gestión dinámica de recursos.

Ya hay un montón de buenas respuestas aquí, pero simplemente desea añadir:
Una explicación simple de RAII es que, en C ++, un objeto asignado en la pila se destruye cada vez que se sale del ámbito. Eso significa, un destructor de objetos será llamada y puede hacer toda la limpieza necesaria.
Eso significa que, si se crea un objeto sin "nuevo", no "eliminar" se requiere. Y esto también es la idea detrás de "punteros inteligentes" -. Residan en la pila, y esencialmente se ajusta un objeto basado montón

RAII es un acrónimo de Raii.

Esta técnica es muy única para C ++ debido a su apoyo, tanto para constructores y destructores y casi automáticamente los constructores que se pongan en venta que los argumentos que se pasan en o el peor de los casos el constructor por defecto se llama y destructores si explícitamente proporcionado se llama de otra manera el defecto que se agrega por el compilador de C ++ se llama si no escribe un destructor de manera explícita para una clase de C ++. Esto sucede sólo para C ++ objetos que son auto-gestionado - significado que no están utilizando la tienda libre (memoria asignada / libera con nuevo, nuevo [] / delete, delete [] C ++ operadores).

técnica RAII hace uso de esta característica objeto de auto-administrada para manejar los objetos que se crean en el montón / libre de la tienda preguntando explcitly para más memoria utilizando nueva / nuevo [], que debe ser destruido de forma explícita llamando a borrar / Eliminar[]. la clase del objeto de auto-administrada envolverá este otro objeto que se crea en la memoria del montón / sin tienda. Por lo tanto, cuando se ejecuta el constructor del objeto de auto-administrada, el objeto envuelto se crea en la memoria del montón / libre de la tienda y cuando la manija del objeto de auto-gestionado sale de ámbito, destructor de ese objeto de auto-administrada es llamado automáticamente en el que la envuelta objeto es destruido mediante eliminar. Con los conceptos de POO, si envuelves tales objetos dentro de otra clase en el ámbito privado, usted no tiene acceso a las clases envueltos miembros y métodos y esta es la razón por la cual punteros inteligentes (también conocido como manejar clases) están diseñados para. Estos punteros inteligentes exponen el objeto envuelto como objeto escrito al mundo exterior y no permitiendo a invocar cualquier miembros / métodos que el objeto de memoria expuesta se compone de. Tenga en cuenta que los punteros inteligentes tienen diferentes sabores en base a diferentes necesidades. Debe consultar la programación modernos C ++ por Andrei Alexandrescu o aumentar de biblioteca (www.boostorg) aplicación shared_ptr.hpp / documentación para aprender más sobre él. Espero que esto le ayuda a comprender RAII.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top