ventajas que desventajas c++ exception try-catch finally throwable

c++ - desventajas - groovy que es



¿Hay un idioma favorito para imitar la prueba de Java/finalmente en C++? (15)

He estado haciendo Java por varios años, así que no he estado rastreando C ++. ¿Se ha agregado finalmente la cláusula al manejo de excepciones de C ++ en la definición de idioma?

¿Hay un idioma favorito que imita el intento / finalmente de Java?

También me molesta que C ++ no tenga un súper tipo definitivo para todas las posibles excepciones que se puedan lanzar, como la clase Throwable de Java.

Puedo escribir:

try { // do something } catch(...) { // alas, can''t examine the exception // can only do cleanup code and perhaps rethrow, ala: throw; }

ADDENDUM EDIT:

Terminé aceptando la respuesta que tuvo más votos, es decir, uso de destructores para hacer la limpieza. Por supuesto, de mis propios comentarios, está claro que no estoy totalmente de acuerdo con eso. Sin embargo, C ++ es lo que es, por lo que en la tarea de la aplicación que tengo en mente, voy a esforzarme más o menos por adherirme a la práctica comunitaria común. Utilizaré clases de plantilla para envolver recursos que no tienen un destructor de clase (es decir, recursos de biblioteca C), otorgándoles así la semántica de destructor.

NUEVA EDICION DE ADENDO:

Hmm, en lugar de finalmente una característica de cierre, tal vez? Un cierre combinado con el enfoque de ScopeGuard (vea una de las respuestas a continuación) sería una forma de lograr la limpieza con acciones arbitrarias y acceso al contexto del alcance externo del código de limpieza. La limpieza se puede hacer de la manera modificada que se ve en la programación de Ruby, donde suministran bloques de limpieza cuando se abre un recurso. ¿No se considera una característica de cierre para C ++?


Creo que te estás perdiendo el sentido de lo que catch (...) puede hacer.

Usted dice en su ejemplo "por desgracia, no puede examinar la excepción". Bueno, no tienes información sobre el tipo de la excepción. Ni siquiera sabe si es un tipo polimórfico, por lo que incluso si tuviera algún tipo de referencia no dynamic_cast , ni siquiera podría intentar de forma segura un dynamic_cast .

Si conoce ciertas excepciones o jerarquías de excepción con las que puede hacer algo, entonces este es el lugar para atrapar bloques con tipos de nombres explícitos.

catch (...) no suele ser útil en C ++. Se puede usar en lugares que tienen que garantizar que no arrojen, o que solo arrojen ciertas excepciones contraídas. Si está utilizando catch (...) para la limpieza, entonces hay una gran posibilidad de que su código no sea robustamente seguro en ningún caso.

Como se menciona en otras respuestas, si está utilizando objetos locales para administrar recursos (RAII), entonces puede ser sorprendente y esclarecedor la cantidad de bloques de captura que necesita, a menudo, si no necesita hacer nada localmente con una excepción, incluso el El bloqueo try puede ser redundante a medida que permite que las excepciones fluyan hacia el código del cliente que puede responder a ellas sin dejar de garantizar problemas de recursos.

Para responder a su pregunta original, si necesita algún código para ejecutar al final de un bloque, excepción o no excepción, entonces una receta sería.

class LocalFinallyReplacement { ~LocalFinallyReplacement() { /* Finally code goes here */ } }; // ... { // some function... LocalFinallyReplacement lfr; // must be a named object // do something }

Fíjate cómo podemos eliminar por completo el try , catch y throw .

Si tenía datos en la función que se declaró originalmente fuera del bloque try al que necesitaba acceso en el bloque "finally", entonces puede necesitar agregar eso al constructor de la clase auxiliar y almacenarlo hasta el destructor. Sin embargo, en este punto, seriamente reconsideraría si el problema podría resolverse alterando el diseño de los objetos de manejo de recursos locales, ya que implicaría algún inconveniente en el diseño.


Haciendo un uso efectivo de los destructores. Cuando se lanza una excepción en un bloque try, cualquier objeto creado dentro de él se destruirá inmediatamente (y, por lo tanto, se llamará a su destructor).

Esto es diferente de Java, donde no tienes idea de cuándo se llamará al finalizador de un objeto.

ACTUALIZACIÓN : Directamente de la boca del caballo: ¿Por qué C ++ no proporciona una construcción "finalmente"?


He hecho un montón de diseño de clase y diseño de envoltura de plantilla en C ++ durante esos 15 años y lo hice todo el camino de C ++ en términos de limpieza de destructores. Sin embargo, todos los proyectos también involucraban invariablemente el uso de las bibliotecas C que proporcionaban los recursos con la apertura, el uso y el modelo de uso cercano. Una prueba / finalmente significaría que tal recurso puede ser consumido donde debe estar, de una manera completamente robusta, y hacerse con él. El enfoque menos tedioso para programar esa situación. Podría lidiar con todo el otro estado que está sucediendo durante la lógica de esa limpieza sin tener que ser escalado en algún destructor de envoltura.

Hice la mayor parte de mi codificación en C ++ en Windows, por lo que siempre podría recurrir al uso de __try / __ de Microsoft para tales situaciones. (Su manejo estructurado de excepciones tiene algunas capacidades poderosas para interactuar con excepciones). Desafortunadamente, no parece que el lenguaje C alguna vez haya ratificado alguna construcción portátil de manejo de excepciones.

Sin embargo, esa no era la solución ideal, porque no era fácil combinar el código C y C ++ en un bloque de prueba donde se podía lanzar cualquier estilo de excepción. Un bloque finalmente agregado a C ++ habría sido útil para esas situaciones y permitiría la portabilidad.


La respuesta de C ++ es RAII: el destructor del objeto se ejecutará cuando salga del alcance. Ya sea por devolución, por una excepción o lo que sea. Si maneja la excepción en otro lugar, puede estar seguro de que todos los objetos de la función llamada hacia abajo a su controlador serán destruidos adecuadamente al hacer que se invoque su destructor. Lo limpiarán por ti.

Lea http://en.wikipedia.org/wiki/Resource_acquisition_is_initialization


Los destructores C ++ hacen finally redundante. Puede obtener el mismo efecto moviendo el código de limpieza de finalmente a los destructores correspondientes.


Mi $ .02. He estado programando en lenguajes administrados como C # y Java durante años, pero me vi forzado a hacer el cambio a C ++ por motivos de velocidad. Al principio no podía creer cómo tuve que escribir la firma del método dos veces en el archivo de encabezado y luego en el archivo cpp, y no me gustó cómo no había un bloqueo definitivo, y ninguna recolección de basura significó el seguimiento de las pérdidas de memoria en todas partes. ¡Dios mío, no me gustó para nada!

Sin embargo, como dije, me vi obligado a usar C ++. Así que me vi obligado a aprenderlo en serio, y ahora finalmente he entendido todos los modismos de programación como RAII y tengo todas las sutilezas del lenguaje y tal. Me tomó un tiempo, pero ahora veo cuán diferente es el lenguaje en comparación con C # o Java.

¡En estos días, creo que C ++ es el mejor lenguaje que existe! Sí, puedo entender que hay un poco más de lo que llamo ''chaff'' a veces (cosas aparentemente innecesarias para escribir), pero después de usar realmente el lenguaje en serio, he cambiado de opinión completamente.

Solía ​​tener pérdidas de memoria todo el tiempo. Solía ​​escribir todo mi código en el archivo .h porque odiaba la separación del código. No podía entender por qué lo harían. Y solía terminar siempre con estúpidas dependencias de inclusión cíclica, y montones más. Estaba realmente colgado de C # o Java, para mí C ++ era un gran paso hacia abajo. Estos días lo entiendo. Casi nunca tengo fugas de memoria, disfruto de la separación de la interfaz y la implementación, y ya no tengo problemas con las dependencias de ciclo.

Y tampoco extraño el bloqueo final. Para ser honesto, mi opinión es que estos programadores de C ++ a los que les hablas de escribir repetidas acciones de limpieza en bloques de captura me suenan como si fueran simplemente malos programadores de C ++. Quiero decir, no parece que ninguno de los otros programadores de C ++ en este hilo tenga ninguno de los problemas que mencionas. RAII realmente hace finalmente redundante, y en todo caso, es menos trabajo. ¡Escribes un destructor y nunca más tienes que escribir otro! Bueno al menos para ese tipo.

Con respeto, lo que creo que está sucediendo es que solo estás acostumbrado a Java ahora, al igual que yo.


Las funciones de limpieza, en sí mismas, son completamente cojas. Tienen baja cohesión, ya que se espera que realicen una serie de actividades solo relacionadas cuando ocurran. Tienen alto acoplamiento, en el sentido de que necesitan que se modifiquen sus internos cuando cambian las funciones que realmente hacen algo. Debido a esto, son propensos a errores.

El intento ... finalmente construir es un marco para las funciones de limpieza. Es una forma alentada por el lenguaje de escribir un código pésimo. Además, dado que alienta a escribir el mismo código de limpieza una y otra vez, socava el principio DRY.

El modo C ++ es mucho más preferible para estos fines. El código de limpieza para un recurso se escribe exactamente una vez, en el destructor. Está en el mismo lugar que el resto del código para ese recurso, y por lo tanto tiene buena cohesión. El código de limpieza no tiene que ser puesto en módulos no relacionados, y por lo tanto esto reduce el acoplamiento. Está escrito precisamente una vez, cuando está bien diseñado.

Además, la forma C ++ es mucho más uniforme. C ++, con las adiciones de punteros inteligentes, maneja todo tipo de recursos de la misma manera, mientras que Java maneja bien la memoria y proporciona construcciones inadecuadas para liberar otros recursos.

Hay muchos problemas con C ++, pero este no es uno de ellos. Hay formas en que Java es mejor que C ++, pero este no es uno de ellos.

Java estaría mucho mejor con una forma de implementar RAII en lugar de intentar ... finalmente.



Para evitar tener que definir una clase contenedora para cada recurso liberable, puede interesarle ScopeGuard ( http://www.ddj.com/cpp/184403758 ) que le permite a uno crear "limpiadores" sobre la marcha.

Por ejemplo:

FILE* fp = SomeExternalFunction(); // Will automatically call fclose(fp) when going out of scope ScopeGuard file_guard = MakeGuard(fclose, fp);


Un ejemplo de lo difícil que es usar finalmente correctamente.

Abrir y cerrar dos archivos.
Donde quiere garantizar que el archivo esté cerrado correctamente.
Esperar el GC no es una opción, ya que los archivos pueden reutilizarse.

En C ++

void foo() { std::ifstream data("plop"); std::ofstream output("plep"); // DO STUFF // Files closed auto-magically }

En un lenguaje sin destructores, pero tiene una cláusula finally.

void foo() { File data("plop"); File output("plep"); try { // DO STUFF } finally { // Must guarantee that both files are closed. try {data.close();} catch(Throwable e){/*Ignore*/} try {output.close();}catch(Throwable e){/*Ignore*/} } }

Este es un ejemplo simple y ya el código se está complicando. Aquí solo intentamos reunir 2 recursos simples. Pero a medida que aumenta la cantidad de recursos que se deben administrar y / o aumenta su complejidad, el uso de un bloque finally se vuelve cada vez más difícil de usar correctamente en presencia de excepciones.

El uso de finalmente mueve la responsabilidad del uso correcto al usuario de un objeto. Al utilizar el mecanismo constructor / destructor proporcionado por C ++, transfiere la responsabilidad del uso correcto al diseñador / implementador de la clase. Esto es más seguro desde el punto de vista de la propiedad, ya que el diseñador solo debe hacerlo correctamente una vez en el nivel de clase (en lugar de tener diferentes usuarios que lo intenten y lo hagan correctamente de diferentes maneras).


Pensé en agregar mi propia solución a esto: una especie de contenedor de puntero inteligente para cuando tienes que lidiar con tipos que no son de RAII.

Usado así:

Finaliser< IMAPITable, Releaser > contentsTable; // now contentsTable can be used as if it were of type IMAPITable*, // but will be automatically released when it goes out of scope.

Así que aquí está la implementación de Finaliser:

/* Finaliser Wrap an object and run some action on it when it runs out of scope. (A kind of ''finally.'') * T: type of wrapped object. * R: type of a ''releaser'' (class providing static void release( T* object )). */ template< class T, class R > class Finaliser { private: T* object_; public: explicit Finaliser( T* object = NULL ) { object_ = object; } ~Finaliser() throw() { release(); } Finaliser< T, R >& operator=( T* object ) { if (object_ != object && object_ != NULL) { release(); } object_ = object; return *this; } T* operator->() const { return object_; } T** operator&() { return &object_; } operator T*() { return object_; } private: void release() throw() { R::release< T >( object_ ); } };

... y aquí está el Liberador:

/* Releaser Calls Release() on the object (for use with Finaliser). */ class Releaser { public: template< class T > static void release( T* object ) { if (object != NULL) { object->Release(); } } };

Tengo algunos tipos diferentes de liberadores como este, incluidos uno gratis () y uno para CloseHandle ().


De acuerdo, tengo que agregar una respuesta a los puntos que hiciste en una publicación de respuesta separada: (Sería mucho más conveniente si hubieras editado esto en la pregunta original, para que no termine en la parte inferior a continuación las respuestas a eso.

Si toda la limpieza siempre se hace en destructores, entonces no habría necesidad de tener ningún código de limpieza en un bloque catch, pero C ++ tiene bloques catch donde se realizan acciones de limpieza. De hecho, tiene un bloque de captura (...) donde solo es posible hacer acciones de limpieza (bueno, ciertamente no se puede obtener información de ninguna excepción para hacer ningún registro).

catch tiene un propósito completamente separado, y como programador de Java debes tenerlo en cuenta. La cláusula finally es para acciones de limpieza "incondicionales". No importa cómo se salga el bloque, esto debe hacerse. Catch es para limpieza condicional. Si se lanza este tipo de excepción, debemos realizar algunas acciones adicionales.

La limpieza en un bloque final se realizará ya sea que se haya lanzado una excepción o no, que es lo que siempre se quiere que ocurra cuando existe un código de limpieza.

De Verdad? Si queremos que siempre suceda para este tipo (por ejemplo, siempre queremos cerrar una conexión de base de datos cuando terminemos con ella), entonces ¿por qué no la definimos una vez ? En el tipo en sí? Haga que la conexión de la base de datos se cierre sola, en lugar de tener que probar / finalmente con cada uso que haga de ella.

Ese es el punto en los destructores. Garantizan que cada tipo pueda hacerse cargo de su propia limpieza, cada vez que se utiliza, sin que la persona que llama tenga que pensar en ello.

Los desarrolladores de C ++ desde el primer día han tenido que repetir las acciones de limpieza que aparecen en los bloques catch en el flujo de código que ocurre al salir exitosamente del bloque try. Los programadores Java y C # lo hacen una vez en el bloque finally.

No. Los programadores de C ++ nunca se han visto afectados por eso. C programadores tienen. Y los programadores de C que se dieron cuenta de que C ++ tenían clases, y luego se llamaron a sí mismos programadores de C ++.

Programo en C ++ y C # diariamente, y siento que estoy plagado por la ridícula insistencia de C # de que debo suministrar una cláusula finally (o un bloque de using ) CADA VEZ QUE uso una conexión de base de datos u otra cosa que deba limpiarse.

C ++ me permite especificar de una vez por todas que "cuando hayamos terminado con este tipo, deberíamos realizar estas acciones". No me arriesgo a olvidar liberar la memoria. No me arriesgo a olvidar cerrar manejadores de archivos, sockets o conexiones de bases de datos. Porque mi memoria, mis manijas, tomas de corriente y conexiones db lo hacen ellos mismos.

¿Cómo puede ser preferible tener que escribir un código de limpieza duplicado cada vez que use un tipo? Si necesita ajustar el tipo porque no tiene un destructor, tiene dos opciones sencillas:

  • Busque una biblioteca C ++ adecuada que proporcione este destructor (sugerencia: Boost)
  • Utilice boost :: shared_ptr para envolverlo, y proporcione un functor personalizado en tiempo de ejecución, especificando la limpieza que se realizará.

Cuando escribe software de servidor de aplicaciones como los servidores de la aplicación Java EE Glassfish, JBoss, etc., desea poder capturar y registrar información de excepción, en lugar de dejar que caiga en el suelo. O peor, caer en el tiempo de ejecución y provocar una salida abrupta y desvergonzada del servidor de aplicaciones. Es por eso que es muy conveniente tener una clase base general para cualquier posible excepción. Y C ++ tiene esa clase. std :: excepción.

Han hecho C ++ desde los días de CFront y Java / C # la mayor parte de esta década. Está claro que hay una enorme brecha cultural en la forma en que se abordan las cosas fundamentalmente similares.

No, nunca has hecho C ++. Has hecho CFront o C con clases. No C ++. Hay una gran diferencia Deja de llamar las respuestas cojo, y es posible que aprendas algo sobre el idioma que creías conocer. ;)


En cuanto a su edición de adiciones, sí se están considerando cierres para C ++ 0x. Se pueden usar con guardias de alcance RAII para proporcionar una solución fácil de usar, consulte el weblog de Pizer . También se pueden usar para imitar try-finally, ver esta respuesta ; pero, ¿ es realmente una buena idea? .


No, finalmente no se ha agregado a C ++, ni es probable que se agregue nunca.

La forma en que C ++ utiliza constructor / destructor hace que la necesidad de finalmente sea innecesaria.
Si usa catch (...) para la limpieza, entonces no está usando C ++ correctamente. El código de limpieza debería estar en el destructor.

Aunque no es un requisito para usarlo, C ++ tiene una excepción std ::.
Obligar a los desarrolladores a derivar de una clase específica para usar la excepción va en contra de la filosofía de mantenerlo simple de C ++. También es por eso que no requerimos que todas las clases se derivan de Object.

Lea: ¿C ++ admite bloques ''finalmente''? (¿Y de qué se trata este ''RAII''?)

El uso de finalmente es más propenso a errores que los destructores para hacer la limpieza.
Esto se debe a que está obligando al usuario del objeto a realizar la limpieza en lugar de al diseñador / implementador de la clase.


Usando C ++ 11 con sus expresiones lambda , recientemente comencé a usar el siguiente código para imitar finally :

class FinallyGuard { private: std::function<void()> f_; public: FinallyGuard(std::function<void()> f) : f_(f) { } ~FinallyGuard() { f_(); } }; void foo() { // Code before the try/finally goes here { // Open a new scope for the try/finally FinallyGuard signalEndGuard([&]{ // Code for the finally block goes here }); // Code for the try block goes here } // End scope, will call destructor of FinallyGuard // Code after the try/finally goes here }

El FinallyGuard es un objeto que se construye con un argumento de función invocable, preferiblemente una expresión lambda. Simplemente recordará esa función hasta que se invoque su destructor, que es el caso cuando el objeto sale del alcance, ya sea debido al flujo de control normal o debido al desenrollado de la pila durante el manejo de excepciones. En ambos casos, el destructor llamará a la función y ejecutará el código en cuestión.

Es un poco extraño que tengas que escribir el código para el finally antes del código para el bloque try , pero aparte de eso, en realidad se siente mucho como un try / finally genuino de Java. Supongo que no se debe abusar de esto para situaciones en las que un objeto con su propio destructor apropiado sería más apropiado, pero hay casos en los que considero que este enfoque anterior es más adecuado. Discutí uno de estos escenarios en esta pregunta .

Por lo que yo entiendo, std::function<void()> usará alguna indirección de puntero y al menos una llamada de función virtual para realizar su borrado de tipo , por lo que habrá una sobrecarga de rendimiento . No use esta técnica en un circuito cerrado donde el rendimiento es crítico. En esos casos, un objeto especializado cuyo destructor hace una sola cosa sería más apropiado.