¿Debo usar un especificador de excepción en C ++?


123

En C ++, puede especificar que una función puede o no generar una excepción mediante el uso de un especificador de excepción. Por ejemplo:

void foo() throw(); // guaranteed not to throw an exception
void bar() throw(int); // may throw an exception of type int
void baz() throw(...); // may throw an exception of some unspecified type

Dudo de usarlos realmente por lo siguiente:

  1. El compilador realmente no aplica los especificadores de excepción de manera rigurosa, por lo que los beneficios no son excelentes. Idealmente, le gustaría obtener un error de compilación.
  2. Si una función viola un especificador de excepción, creo que el comportamiento estándar es terminar el programa.
  3. En VS.Net, trata el lanzamiento (X) como un lanzamiento (...), por lo que el cumplimiento del estándar no es fuerte.

¿Crees que deberían usarse los especificadores de excepción?
Responda "sí" o "no" y proporcione algunas razones para justificar su respuesta.


77
"throw (...)" no es C ++ estándar. Creo que es una extensión en algunos compiladores y generalmente tiene el mismo significado que ninguna especificación de excepción.
Richard Corden

Respuestas:


97

No.

Aquí hay varios ejemplos de por qué:

  1. El código de plantilla es imposible de escribir con especificaciones de excepción,

    template<class T>
    void f( T k )
    {
         T x( k );
         x.x();
    }

    Las copias pueden arrojarse, el paso de parámetros puede arrojarse y x()arrojar alguna excepción desconocida.

  2. Las especificaciones de excepción tienden a prohibir la extensibilidad.

    virtual void open() throw( FileNotFound );

    podría evolucionar en

    virtual void open() throw( FileNotFound, SocketNotReady, InterprocessObjectNotImplemented, HardwareUnresponsive );

    Realmente podrías escribir eso como

    throw( ... )

    El primero no es extensible, el segundo es demasiado ambicioso y el tercero es realmente lo que quieres decir cuando escribes funciones virtuales.

  3. Código heredado

    Cuando escribe código que se basa en otra biblioteca, realmente no sabe lo que podría hacer cuando algo sale terriblemente mal.

    int lib_f();
    
    void g() throw( k_too_small_exception )
    { 
       int k = lib_f();
       if( k < 0 ) throw k_too_small_exception();
    }

    gterminará, cuando lib_f()lance. Esto (en la mayoría de los casos) no es lo que realmente quieres. std::terminate()nunca debe ser llamado Siempre es mejor dejar que la aplicación se bloquee con una excepción no controlada, de la que puede recuperar un seguimiento de la pila, que morir en silencio / violentamente.

  4. Escriba código que devuelva errores comunes y tiros en ocasiones excepcionales.

    Error e = open( "bla.txt" );
    if( e == FileNotFound )
        MessageUser( "File bla.txt not found" );
    if( e == AccessDenied )
        MessageUser( "Failed to open bla.txt, because we don't have read rights ..." );
    if( e != Success )
        MessageUser( "Failed due to some other error, error code = " + itoa( e ) );
    
    try
    {
       std::vector<TObj> k( 1000 );
       // ...
    }
    catch( const bad_alloc& b )
    { 
       MessageUser( "out of memory, exiting process" );
       throw;
    }

Sin embargo, cuando su biblioteca solo arroja sus propias excepciones, puede usar especificaciones de excepción para indicar su intención.


1
en 3, técnicamente sería std :: inesperado no std :: terminar. Pero cuando se llama a esta función, el valor predeterminado invoca abort (). Esto genera un volcado de núcleo. ¿Cómo es esto peor que una excepción no controlada? (que hace básicamente lo mismo)
Greg Rogers

66
@ Greg Rogers: una excepción no capturada todavía se desbobina. Esto significa que se llamarán destructores. Y en esos destructores, se puede hacer mucho, como: recursos liberados correctamente, registros correctamente escritos, otros procesos recibirán información de que el proceso actual se está bloqueando, etc. Para resumir, es RAII.
paercebal

Te quedaste afuera: esto efectivamente envuelve todo en una try {...} catch (<specified exceptions>) { <do whatever> } catch (...) { unexpected(); ]construcción, ya sea que quieras o no un bloque de prueba allí.
David Thornley

44
@paercebal Eso es incorrecto. Su implementación se define si los destructores se ejecutan o no para excepciones no detectadas. La mayoría de los entornos no desenrollan los destructores de pila / ejecución si no se detecta la excepción. Si desea asegurarse de que sus destructores se ejecuten de manera portátil incluso cuando se lanza una excepción y no se maneja (esto es de dudoso valor), debe escribir su código comotry { <<...code...>> } catch(...) /* stack guaranteed to be unwound here and dtors run */ { throw; /* pass it on to the runtime */ }
Logan Capaldo

" Siempre es mejor dejar que la aplicación se bloquee con una excepción no controlada, de la que puede recuperar un rastro de la pila, que morir en silencio / violentamente " . ¿Cómo puede ser mejor un "bloqueo" que una llamada limpia terminate()? ¿Por qué no solo llamas abort()?
curioso

42

Evite las especificaciones de excepción en C ++. Las razones que da en su pregunta son un buen comienzo de por qué.

Vea "Una mirada pragmática a las especificaciones de excepción" de Herb Sutter .


3
@awoodland: el uso de 'dynamic-excepción-especificaciones' ( throw(optional-type-id-list)) está en desuso en C ++ 11. Todavía están en el estándar, pero supongo que se ha enviado un disparo de advertencia de que su uso debe considerarse con cuidado. C ++ 11 agrega la noexceptespecificación y el operador. No conozco suficientes detalles sobrenoexcept para comentarlo. Este artículo parece ser bastante detallado: akrzemi1.wordpress.com/2011/06/10/using-noexcept y Dietmar Kühl tiene un artículo en el Overload Journal de junio de 2011: accu.org/var/uploads/journals/overload103.pdf
Michael Burr

@MichaelBurr Only throw(something)se considera inútil y una mala idea. throw()es útil.
curioso

" Esto es lo que mucha gente piensa que hacen las especificaciones de excepción: bullet Garantiza que las funciones solo arrojarán excepciones enumeradas (posiblemente ninguna). Bullet Habilita las optimizaciones del compilador basadas en el conocimiento de que solo se lanzarán excepciones enumeradas (posiblemente ninguna). Las expectativas anteriores son, de nuevo, engañosamente cerca de ser correcto "No, las expectativas anteriores son absolutamente correctas.
curioso

14

Creo que los especificadores de excepción estándar (excepto para C ++)
fueron un experimento en el estándar C ++ que falló en su mayoría.
La excepción es que el especificador no throw es útil, pero también debe agregar internamente el bloque try catch apropiado para asegurarse de que el código coincida con el especificador. Herb Sutter tiene una página sobre el tema. Gotch 82

Además, creo que vale la pena describir las garantías de excepción.

Básicamente, se trata de documentación sobre cómo el estado de un objeto se ve afectado por las excepciones que escapan de un método en ese objeto. Lamentablemente, el compilador no los aplica ni los menciona.
Impulso y excepciones

Garantías de excepción

Sin garantía:

No hay garantía sobre el estado del objeto después de que una excepción escapa a un método.
En estas situaciones, el objeto ya no se debe usar.

Garantía básica:

En casi todas las situaciones, esta debería ser la garantía mínima que proporciona un método.
Esto garantiza que el estado del objeto está bien definido y aún se puede usar de manera consistente.

Garantía fuerte: (también conocida como Garantía transaccional)

Esto garantiza que el método se completará con éxito
o se lanzará una excepción y el estado de los objetos no cambiará.

Garantía sin lanzamiento:

El método garantiza que no se permite propagar excepciones fuera del método.
Todos los destructores deben hacer esta garantía.
El | NB Si una excepción escapa a un destructor mientras una excepción ya se está propagando
| la aplicación terminará


Las garantías son algo que todo programador de C ++ debe saber, pero no me parecen estar relacionadas con las especificaciones de excepción.
David Thornley

1
@David Thornley: veo las garantías como lo que deberían haber sido las especificaciones de excepción (es decir, un método con la G fuerte no puede llamar a un método con una G básica sin protección). Desafortunadamente, no estoy seguro de que estén bien definidos como para ser implementados de manera útil como compilador.
Martin York

" Esto es lo que mucha gente piensa que hacen las especificaciones de excepción: - Garantizar que las funciones solo arrojarán excepciones enumeradas (posiblemente ninguna). - Habilitar optimizaciones del compilador basadas en el conocimiento de que solo se lanzarán excepciones enumeradas (posiblemente ninguna). Las expectativas anteriores son, de nuevo, engañosamente cerca de ser correcta. "en realidad, ambos son exactamente correcto.
curioso

@curiousguy. Así es como lo hace Java porque las verificaciones se aplican en tiempo de compilación. C ++ son verificaciones de tiempo de ejecución. Por lo tanto: Guarantee that functions will only throw listed exceptions (possibly none). No es verdad. Solo garantiza que si la función arroja estas excepciones, la aplicación finalizará. Enable compiler optimizations based on the knowledge that only listed exceptions (possibly none) will be thrownNo puedo hacer eso Porque acabo de señalar que no puede garantizar que no se lanzará la excepción.
Martin York

1
@LokiAstari "Así es como lo hace Java porque las verificaciones se aplican en tiempo de compilación_" La especificación de excepción de Java es un experimento que falló. Java no tiene la especificación de excepción más útil: throw()(no arroja). " Solo garantiza que si la función arroja estas excepciones, la aplicación terminará " No "no es verdadero" significa verdadero. No hay garantía en C ++ de que una función no llame terminate()nunca. " Porque acabo de señalar que no puede garantizar que no se lanzará la excepción " Usted garantiza que la función no se lanzará. Que es exactamente lo que necesitas.
curioso

8

gcc emitirá advertencias cuando viole las especificaciones de excepción. Lo que hago es usar macros para usar las especificaciones de excepción solo en un modo "pelusa" compilar expresamente para verificar que las excepciones estén de acuerdo con mi documentación.


7

El único especificador de excepción útil es "throw ()", como en "no arroja".


2
¿Puedes agregar una razón por la que es útil?
buti-oxa

2
¿Por qué no es útil? no hay nada más útil que saber que alguna función no comenzará a lanzar excepciones hacia la derecha y hacia el centro.
Matt Joiner, el

1
Consulte la discusión sobre Herb Sutter a la que se hace referencia en la respuesta de Michael Burr para obtener una explicación detallada.
Harold Ekstrom

4

Las especificaciones de excepción no son herramientas maravillosamente útiles en C ++. Sin embargo, hay / es / un buen uso para ellos, si se combina con std :: inesperado.

Lo que hago en algunos proyectos es código con especificaciones de excepción, y luego llamar a set_unexpected () con una función que arrojará una excepción especial de mi propio diseño. Esta excepción, después de la construcción, obtiene una traza inversa (de manera específica de la plataforma) y se deriva de std :: bad_exception (para permitir que se propague si se desea). Si causa una llamada terminate (), como suele suceder, el rastreo se imprime con what () (así como la excepción original que lo causó; no es difícil encontrarlo) y así obtengo información de dónde estaba mi contrato violado, como qué excepción inesperada de la biblioteca se produjo.

Si hago esto, nunca permitiré la propagación de excepciones de la biblioteca (excepto las estándar) y derivar todas mis excepciones de std :: exception. Si una biblioteca decide lanzar, atraparé y convertiré en mi propia jerarquía, lo que me permitirá controlar siempre el código. Las funciones con plantilla que llaman a funciones dependientes deben evitar especificaciones de excepción por razones obvias; pero es raro tener una interfaz de función con plantilla con código de biblioteca de todos modos (y pocas bibliotecas realmente usan plantillas de manera útil).


3

Si está escribiendo un código que será utilizado por personas que preferirían mirar la declaración de la función que cualquier comentario al respecto, entonces una especificación les dirá qué excepciones pueden querer atrapar.

De lo contrario, no me parece particularmente útil usar nada más throw()que indicar que no arroja ninguna excepción.


3

No. Si los usa y se produce una excepción que no especificó, ya sea por su código o por el código invocado por su código, entonces el comportamiento predeterminado es terminar rápidamente su programa.

Además, creo que su uso ha quedado en desuso en los borradores actuales del estándar C ++ 0x.



2

Generalmente no usaría especificadores de excepción. Sin embargo, en los casos en los que, si alguna otra excepción viniera de la función en cuestión, el programa definitivamente no podría corregir , entonces puede ser útil. En todos los casos, asegúrese de documentar claramente qué excepciones podrían esperarse de esa función.

Sí, el comportamiento esperado de una excepción no especificada lanzada desde una función con especificadores de excepción es llamar a terminate ().

También señalaré que Scott Meyers aborda este tema en C ++ más eficaz. Su C ++ efectivo y C ++ más efectivo son libros muy recomendados.


2

Sí, si te interesa la documentación interna. O tal vez escribir una biblioteca que otros usarán, para que puedan saber lo que sucede sin consultar la documentación. Lanzar o no lanzar puede considerarse parte de la API, casi como el valor de retorno.

Estoy de acuerdo, no son realmente útiles para forzar la corrección del estilo Java en el compilador, pero es mejor que nada o comentarios casuales.


2

Pueden ser útiles para las pruebas unitarias, de modo que al escribir pruebas sepa qué esperar que la función arroje cuando falla, pero no hay cumplimiento que las rodee en el compilador. Creo que son códigos adicionales que no son necesarios en C ++. Cualquiera que elija, de lo que debe estar seguro es que sigue el mismo estándar de codificación en todo el proyecto y los miembros del equipo para que su código siga siendo legible.


0

Del artículo:

http://www.boost.org/community/exception_safety.html

"Es bien sabido que es imposible escribir un contenedor genérico seguro para excepciones". Esta afirmación a menudo se escucha con referencia a un artículo de Tom Cargill [4] en el que explora el problema de la seguridad de excepción para una plantilla de pila genérica. En su artículo, Cargill plantea muchas preguntas útiles, pero desafortunadamente no presenta una solución a su problema.1 Concluye sugiriendo que una solución puede no ser posible. Desafortunadamente, su artículo fue leído por muchos como "prueba" de esa especulación. Desde su publicación, ha habido muchos ejemplos de componentes genéricos seguros para excepciones, entre ellos los contenedores de biblioteca estándar de C ++.

Y de hecho, puedo pensar en formas de hacer que las clases de plantillas sean seguras. A menos que no tenga control sobre todas las subclases, puede tener un problema de todos modos. Para hacer esto, podría crear typedefs en sus clases que definan las excepciones lanzadas por varias clases de plantillas. Esto piensa que el problema es siempre abordarlo después en lugar de diseñarlo desde el principio, y creo que esta sobrecarga es el verdadero obstáculo.


-2

Especificaciones de excepción = basura, pregunte a cualquier desarrollador de Java mayor de 30 años


8
Los programadores de Java mayores de 30 años deberían sentirse mal por no poder hacer frente a C, no tienen excusa para usar Java.
Matt Joiner,
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.