¿C ++ admite bloques ' finalmente '?
¿Cuál es el idioma de RAII ?
¿Cuál es la diferencia entre el lenguaje RAII de C ++ y la declaración 'usando' de C # ?
¿C ++ admite bloques ' finalmente '?
¿Cuál es el idioma de RAII ?
¿Cuál es la diferencia entre el lenguaje RAII de C ++ y la declaración 'usando' de C # ?
Respuestas:
No, C ++ no admite bloques 'finalmente'. La razón es que C ++ en su lugar admite RAII: "La adquisición de recursos es la inicialización", un nombre pobre † para un concepto realmente útil.
La idea es que el destructor de un objeto es responsable de liberar recursos. Cuando el objeto tiene una duración de almacenamiento automática, se llamará al destructor del objeto cuando salga el bloque en el que se creó, incluso cuando ese bloque salga en presencia de una excepción. Aquí está la explicación del tema por Bjarne Stroustrup .
Un uso común para RAII es bloquear un mutex:
RAII también simplifica el uso de objetos como miembros de otras clases. Cuando se destruye la clase propietaria, el recurso administrado por la clase RAII se libera porque el destructor para la clase administrada por RAII se invoca como resultado. Esto significa que cuando usa RAII para todos los miembros en una clase que administra recursos, puede salirse con la suya usando un destructor muy simple, tal vez incluso el predeterminado, para la clase propietaria, ya que no necesita administrar manualmente la vida útil de los recursos de sus miembros. . (Gracias a Mike B por señalar esto).
Para aquellos que están familiarizados con C # o VB.NET, puede reconocer que RAII es similar a la destrucción determinista de .NET usando las declaraciones IDisposable y 'using' . De hecho, los dos métodos son muy similares. La principal diferencia es que RAII liberará de manera determinista cualquier tipo de recurso, incluida la memoria. Al implementar IDisposable en .NET (incluso el lenguaje .NET C ++ / CLI), los recursos se liberarán de forma determinista, excepto para la memoria. En .NET, la memoria no se libera de manera determinista; la memoria solo se libera durante los ciclos de recolección de basura.
† Algunas personas creen que "La destrucción es la renuncia a los recursos" es un nombre más exacto para el lenguaje RAII.
fuente
En C ++, finalmente NO se requiere debido a RAII.
RAII traslada la responsabilidad de la seguridad de excepción del usuario del objeto al diseñador (e implementador) del objeto. Diría que este es el lugar correcto, ya que solo necesita obtener una excepción de seguridad correcta una vez (en el diseño / implementación). Al usar finalmente, debe obtener una seguridad de excepción correcta cada vez que use un objeto.
También IMO el código se ve más ordenado (ver más abajo).
Ejemplo:
Un objeto de base de datos. Para asegurarse de que se utiliza la conexión DB, debe abrirse y cerrarse. Al usar RAII esto se puede hacer en el constructor / destructor.
C ++ como RAII
El uso de RAII hace que usar un objeto DB correctamente sea muy fácil. El objeto DB se cerrará correctamente mediante el uso de un destructor, sin importar cómo intentemos abusar de él.
Java como finalmente
Cuando se usa finalmente, el uso correcto del objeto se delega al usuario del objeto. es decir , es responsabilidad del usuario del objeto cerrar correctamente la conexión de base de datos. Ahora podría argumentar que esto se puede hacer en el finalizador, pero los recursos pueden tener disponibilidad limitada u otras restricciones y, por lo tanto, generalmente desea controlar la liberación del objeto y no confiar en el comportamiento no determinista del recolector de basura.
También este es un ejemplo simple.
Cuando tiene múltiples recursos que necesitan ser liberados, el código puede complicarse.
Un análisis más detallado se puede encontrar aquí: http://accu.org/index.php/journals/236
fuente
// Make sure not to throw exception if one is already propagating.
Es importante que los destructores de C ++ no arrojen excepciones también por esta misma razón.RAII es generalmente mejor, pero usted puede fácilmente tener los finalmente la semántica en C ++. Usando una pequeña cantidad de código.
Además, las Pautas principales de C ++ dan finalmente.
Aquí hay un enlace a la implementación de GSL Microsoft y un enlace a la implementación de Martin Moene
Bjarne Stroustrup dijo varias veces que todo lo que está en la GSL significa que eventualmente entrará en el estándar. Por lo tanto, debería ser una forma a prueba de futuro para usar finalmente .
Sin embargo, puede implementarse fácilmente si lo desea, continúe leyendo.
En C ++ 11 RAII y lambdas permite hacer un general finalmente:
ejemplo de uso:
la salida será:
Personalmente, lo usé varias veces para asegurarme de cerrar el descriptor de archivo POSIX en un programa C ++.
Por lo general, es mejor tener una clase real que administre recursos y evite cualquier tipo de fugas, pero esto finalmente es útil en los casos en que hacer que una clase suene como una exageración.
Además, finalmente me gusta más que otros idiomas porque si se usa de forma natural, escribe el código de cierre cerca del código de apertura (en mi ejemplo, el nuevo y el borrado ) y la destrucción sigue a la construcción en orden LIFO como es habitual en C ++. El único inconveniente es que obtienes una variable automática que realmente no usas y la sintaxis lambda lo hace un poco ruidoso (en mi ejemplo en la cuarta línea, solo la palabra finalmente y el bloque {} a la derecha son significativos, el el descanso es esencialmente ruido).
Otro ejemplo:
El miembro deshabilitado es útil si finalmente se debe llamar solo en caso de falla. Por ejemplo, debe copiar un objeto en tres contenedores diferentes, puede configurar finalmente para deshacer cada copia y deshabilitarla después de que todas las copias sean exitosas. Si lo hace, si la destrucción no puede lanzar, se asegura la garantía fuerte.
desactivar ejemplo:
Si no puede usar C ++ 11, todavía puede tenerlo finalmente , pero el código se vuelve un poco más largo. Simplemente defina una estructura con solo un constructor y un destructor, el constructor toma referencias a todo lo necesario y el destructor realiza las acciones que necesita. Esto es básicamente lo que hace el lambda, hecho manualmente.
fuente
FinalAction
es básicamente lo mismo que elScopeGuard
idioma popular , solo que con un nombre diferente.Más allá de facilitar la limpieza con objetos basados en pila, RAII también es útil porque la misma limpieza 'automática' ocurre cuando el objeto es miembro de otra clase. Cuando se destruye la clase propietaria, el recurso administrado por la clase RAII se limpia porque el dtor para esa clase se llama como resultado.
Esto significa que cuando alcanza el nirvana RAII y todos los miembros de una clase usan RAII (como punteros inteligentes), puede salirse con un dtor muy simple (tal vez incluso predeterminado) para la clase propietaria, ya que no necesita administrar manualmente su duración de los recursos de los miembros.
fuente
En realidad, los lenguajes basados en recolectores de basura necesitan "finalmente" más. Un recolector de basura no destruye sus objetos de manera oportuna, por lo que no se puede confiar para limpiar correctamente los problemas no relacionados con la memoria.
En términos de datos asignados dinámicamente, muchos argumentan que debería estar utilizando punteros inteligentes.
Sin embargo...
Lamentablemente, esta es su propia caída. Los viejos hábitos de programación C mueren con dificultad. Cuando está utilizando una biblioteca escrita en C o en un estilo muy C, RAII no se habrá utilizado. A menos que vuelva a escribir todo el front-end de API, eso es exactamente con lo que tiene que trabajar. Entonces la falta de "finalmente" realmente muerde.
fuente
CleanupFailedException
. ¿Hay alguna forma plausible de lograr ese resultado utilizando RAII?SomeObject.DoSomething()
método y querrá saber si (1) tuvo éxito, (2) falló sin efectos secundarios , (3) falló con efectos secundarios que la persona que llama está preparada para hacer frente , o (4) fallaron con los efectos secundarios que la persona que llama no puede hacer frente. Solo la persona que llama sabrá qué situaciones puede y no puede hacer frente; lo que necesita la persona que llama es una forma de saber cuál es la situación. Es una pena que no haya un mecanismo estándar para proporcionar la información más importante sobre una excepción.Otra emulación de bloque "finalmente" usando las funciones lambda de C ++ 11
Esperemos que el compilador optimice el código anterior.
Ahora podemos escribir código como este:
Si lo desea, puede envolver este modismo en macros "intente - finalmente":
Ahora el bloque "finalmente" está disponible en C ++ 11:
Personalmente, no me gusta la versión "macro" del idioma "finalmente" y preferiría usar la función pura "with_finally" aunque la sintaxis sea más voluminosa en ese caso.
Puede probar el código anterior aquí: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813
PD
Si finalmente necesita un bloque en su código, los guardias de alcance o las macros ON_FINALLY / ON_EXCEPTION probablemente se adaptarán mejor a sus necesidades.
Aquí hay un breve ejemplo de uso ON_FINALLY / ON_EXCEPTION:
fuente
Perdón por desenterrar un hilo tan antiguo, pero hay un error importante en el siguiente razonamiento:
En la mayoría de los casos, debe tratar con objetos asignados dinámicamente, números dinámicos de objetos, etc. Dentro del bloque try, algunos códigos pueden crear muchos objetos (cuántos se determinan en tiempo de ejecución) y almacenar punteros en una lista. Ahora, este no es un escenario exótico, sino muy común. En este caso, querrás escribir cosas como
Por supuesto, la lista en sí se destruirá cuando salga del alcance, pero eso no limpiaría los objetos temporales que ha creado.
En cambio, tienes que ir por la ruta fea:
Además: ¿por qué incluso los idiomas administrados proporcionan un bloqueo final a pesar de que los recursos se desasignan automáticamente por el recolector de basura de todos modos?
Sugerencia: hay más cosas que puedes hacer con "finalmente" que solo desasignación de memoria.
fuente
new
no devuelve NULL, lanza una excepción en su lugarstd::shared_ptr
ystd::unique_ptr
directamente en stdlib.FWIW, Microsoft Visual C ++ admite probar, finalmente, e históricamente se ha utilizado en aplicaciones MFC como un método para detectar excepciones graves que de lo contrario provocarían un bloqueo. Por ejemplo;
Lo he usado en el pasado para hacer cosas como guardar copias de seguridad de archivos abiertos antes de salir. Sin embargo, ciertas configuraciones de depuración JIT romperán este mecanismo.
fuente
Como se señaló en las otras respuestas, C ++ puede admitir una
finally
funcionalidad similar. La implementación de esta funcionalidad que probablemente esté más cerca de ser parte del lenguaje estándar es la que acompaña a las Pautas Básicas de C ++ , un conjunto de mejores prácticas para usar C ++ editado por Bjarne Stoustrup y Herb Sutter. Una implementación definally
es parte de la Biblioteca de soporte de guías (GSL). A lo largo de las Pautas,finally
se recomienda el uso de cuando se trata con interfaces de estilo antiguo, y también tiene una pauta propia, titulada Usar un objeto final_action para expresar la limpieza si no hay disponible un manejador de recursos adecuado .Por lo tanto, no solo es compatible con C ++
finally
, en realidad se recomienda usarlo en muchos casos de uso comunes.Un ejemplo de uso de la implementación GSL sería:
La implementación y el uso de GSL son muy similares a los de la respuesta de Paolo.Bolzoni . Una diferencia es que el objeto creado por
gsl::finally()
carece de ladisable()
llamada. Si necesita esa funcionalidad (por ejemplo, para devolver el recurso una vez que está ensamblado y no se producirán excepciones), es posible que prefiera la implementación de Paolo. De lo contrario, usar GSL es lo más parecido a usar funciones estandarizadas que obtendrá.fuente
En realidad no, pero puedes emularlos hasta cierto punto, por ejemplo:
Tenga en cuenta que el bloque finalmente podría lanzar una excepción antes de que se vuelva a lanzar la excepción original, descartando así la excepción original. Este es exactamente el mismo comportamiento que en un bloque Java finalmente. Además, no puede usar
return
dentro de los bloques try & catch.fuente
std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
finally
bloque.Se me ocurrió una
finally
macro que se puede usar casi como ¹ lafinally
palabra clave en Java; utilizastd::exception_ptr
y amigos, funciones lambda ystd::promise
, por lo tanto, requiereC++11
o superior; también hace uso de la expresión de declaración compuesta extensión de GCC de , que también es compatible con clang.ADVERTENCIA : una versión anterior de esta respuesta utilizaba una implementación diferente del concepto con muchas más limitaciones.
Primero, definamos una clase auxiliar.
Luego está la macro real.
Se puede usar así:
El uso de
std::promise
hace que sea muy fácil de implementar, pero probablemente también introduce un poco de sobrecarga innecesaria que podría evitarse reimplementando solo las funcionalidades necesariasstd::promise
.¹ CAVEAT: hay algunas cosas que no funcionan como la versión de Java
finally
. La parte superior de mi cabeza:break
declaración del interior de latry
ecatch()
's bloques, ya que viven dentro de una función lambda;catch()
bloque después detry
: es un requisito de C ++;try
ycatch()'s
, la compilación fallará porque lafinally
macro se expandirá al código que querrá devolver avoid
. Esto podría ser, err, un vacío al tener unafinally_noreturn
especie de macro.En general, no sé si alguna vez usaría estas cosas yo mismo, pero fue divertido jugar con ellas. :)
fuente
catch(xxx) {}
bloque imposible al comienzo de lafinally
macro, donde xxx es un tipo falso solo con el propósito de tener al menos un bloque de captura.catch(...)
, ¿no?xxx
en un espacio de nombres privado que nunca se utilizará.Tengo un caso de uso en el que creo que
finally
debería ser una parte perfectamente aceptable del lenguaje C ++ 11, ya que creo que es más fácil de leer desde el punto de vista del flujo. Mi caso de uso es una cadena de hilos de consumo / productor, donde un centinelanullptr
se envía al final de la ejecución para cerrar todos los hilos.Si C ++ lo admite, desearía que su código se vea así:
Creo que esto es más lógico que poner su declaración final al comienzo del ciclo, ya que ocurre después de que el ciclo ha salido ... pero eso es una ilusión porque no podemos hacerlo en C ++. Tenga en cuenta que la cola
downstream
está conectada a otro hilo, por lo que no puede poner el centinelapush(nullptr)
en el destructordownstream
porque no puede ser destruido en este punto ... necesita mantenerse con vida hasta que el otro hilo reciba elnullptr
.Así que aquí está cómo usar una clase RAII con lambda para hacer lo mismo:
y así es como lo usas:
fuente
Como muchas personas han declarado, la solución es usar las funciones de C ++ 11 para evitar finalmente los bloqueos. Una de las características es
unique_ptr
.Aquí está la respuesta de Mephane escrita usando patrones RAII.
Un poco más de introducción al uso de unique_ptr con contenedores de biblioteca de C ++ estándar es aquí
fuente
Me gustaría ofrecer una alternativa.
Si finalmente desea que se llame siempre al bloque, simplemente colóquelo después del último bloque catch (que probablemente debería ser
catch( ... )
para atrapar una excepción no conocida)Si finalmente desea bloquear como última cosa cuando se produce una excepción, puede usar la variable local booleana: antes de ejecutarla, configúrela como falsa y coloque la asignación verdadera al final del bloque try, luego, después de la captura del bloque, compruebe la variable valor:
fuente
También creo que RIIA no es un reemplazo totalmente útil para el manejo de excepciones y tener un finalmente. Por cierto, también creo que RIIA es un mal nombre en todas partes. Llamo a este tipo de clases 'conserjes' y los uso MUCHO. El 95% de las veces no están inicializando ni adquiriendo recursos, están aplicando algún cambio de forma selectiva, o toman algo ya configurado y se aseguran de que se destruya. Siendo este el patrón oficial de Internet obsesionado, me abusan incluso por sugerir que mi nombre podría ser mejor.
Simplemente no creo que sea razonable exigir que cada configuración complicada de alguna lista ad hoc de cosas tenga que tener una clase escrita para contenerla con el fin de evitar complicaciones al limpiar todo de nuevo ante la necesidad de atrapar múltiples tipos de excepción si algo sale mal en el proceso. Esto llevaría a muchas clases ad hoc que de otro modo no serían necesarias.
Sí, está bien para las clases que están diseñadas para administrar un recurso en particular, o genéricas que están diseñadas para manejar un conjunto de recursos similares. Pero, incluso si todas las cosas involucradas tienen tales envoltorios, la coordinación de la limpieza puede no ser una simple invocación de destructores en orden inverso.
Creo que tiene mucho sentido que C ++ tenga un finalmente. Quiero decir, caramba, se han pegado tantas partes y bobs en las últimas décadas que parece extraño que la gente de repente se vuelva conservadora sobre algo que finalmente podría ser bastante útil y probablemente nada tan complicado como otras cosas que han sido agregado (aunque eso es solo una suposición de mi parte).
fuente
fuente
finally
que no hace.