¿Debo heredar de std :: exception?

98

He visto al menos una fuente confiable (una clase de C ++ que tomé) que recomienda que las clases de excepción específicas de la aplicación en C ++ deberían heredar std::exception. No tengo claros los beneficios de este enfoque.

En C #, las razones para heredar de ApplicationExceptionson claras: obtienes un puñado de métodos, propiedades y constructores útiles y solo tienes que agregar o anular lo que necesitas. Con std::exceptionparece que lo único que consigue es un what()método para anular, que sólo podría así crear tú mismo.

Entonces, ¿cuáles son los beneficios, si los hay, de usar std::exceptioncomo clase base para mi clase de excepción específica de la aplicación? ¿Existen buenas razones para no heredar std::exception?

John M. Gant
fuente
Es posible que desee echar un vistazo a esto: stackoverflow.com/questions/1605778/1605852#1605852
sbi
2
Aunque como una nota al margen no relacionada con la pregunta en particular, las clases de C ++ que tome no tienen por qué ser necesariamente fuentes confiables sobre buenas prácticas por derecho propio.
Christian Rau

Respuestas:

69

El principal beneficio es que el código que usa sus clases no tiene que saber el tipo exacto de lo que está throwhaciendo, sino que puede simplemente catchel std::exception.

Editar: como señalaron Martin y otros, en realidad desea derivar de una de las subclases de std::exceptiondeclarado en <stdexcept>header.

Nikolai Fetissov
fuente
21
No hay forma de pasar un mensaje a std :: exception. std :: runtime_error acepta una cadena y se deriva de std :: exception.
Martin York
14
No debe pasar un mensaje al constructor del tipo de excepción (considere que los mensajes deben ser localizados). En su lugar, defina un tipo de excepción que categorice el error semánticamente, almacene en el objeto de excepción lo que necesita para formatear un mensaje fácil de usar. , luego hágalo en el sitio de captura.
Emil
std::exceptionse define en el <exception>encabezado ( prueba ). -1
Alexander Shukaev
5
Entonces, ¿cuál es tu punto? La definición implica declaración, ¿qué ves que está mal aquí?
Nikolai Fetissov
40

El problema std::exceptiones que no hay ningún constructor (en las versiones compatibles con el estándar) que acepte un mensaje.

Como resultado, prefiero derivar de std::runtime_error. Esto se deriva de, std::exceptionpero sus constructores le permiten pasar una C-String o std::stringa al constructor que se devolverá (como a char const*) cuando what()se llame.

Martin York
fuente
11
No es una buena idea formatear un mensaje fácil de usar en el punto del lanzamiento porque esto combinaría el código de nivel inferior con la funcionalidad de localización y todo eso. En su lugar, almacene en el objeto de excepción toda la información relevante y deje que el sitio de captura dé formato a un mensaje fácil de usar según el tipo de excepción y los datos que contiene.
Emil
16
@ Emil: Claro que si es una excepción, lleve mensajes visualizables por el usuario, aunque generalmente son solo para fines de registro. En el sitio de lanzamiento, no tiene el contexto para crear un mensaje de usuario (ya que de todos modos puede ser una biblioteca). El sitio de captura tendrá más contexto y puede generar el mensaje apropiado.
Martin York
3
No sé de dónde sacaste la idea de que las excepciones son solo para fines de registro. :)
Emil
1
@Emil: Ya hemos pasado por este argumento. Donde señalé que eso no es lo que está poniendo en la excepción. Su comprensión del tema parece buena, pero su argumento es defectuoso. Los mensajes de error generados para el usuario son completamente diferentes de los mensajes de registro para el desarrollador.
Martin York
1
@Emil. Esta discusión tiene un año. Crea tu propia pregunta en este momento. En lo que a mí respecta, sus argumentos son simplemente erróneos (y según los votos la gente tiende a estar de acuerdo conmigo). Consulte ¿Cuál es un "buen número" de excepciones para implementar en mi biblioteca? y ¿Es realmente malo detectar excepciones generales? No ha proporcionado ninguna nueva información desde su diatriba anterior.
Martin York
17

La razón para heredar std::exceptiones la clase base "estándar" para las excepciones, por lo que es natural que otras personas en un equipo, por ejemplo, esperen eso y atrapen la base std::exception.

Si busca comodidad, puede heredar de lo std::runtime_errorque proporciona std::stringconstructor.

Aleksei Potov
fuente
2
Derivar de cualquier otro tipo de excepción estándar excepto std :: exception probablemente no sea una buena idea. El problema es que derivan de std :: exception de forma no virtual, lo que puede conducir a errores sutiles que involucran herencia múltiple donde una captura (std :: excepción &) podría fallar silenciosamente al detectar una excepción debido a que la conversión a std :: excepción es ambiguo.
Emil
2
Leí el artículo sobre impulso sobre el tema. Parece razonable en un sentido puramente lógico. Pero nunca he visto a MH en excepciones en la naturaleza. Tampoco consideraría usar MH en excepciones, por lo que parece un mazo para solucionar un problema inexistente. Si se tratara de un problema real, estoy seguro de que habríamos visto medidas al respecto por parte del comité de normas para corregir un defecto tan obvio. Entonces, mi opinión es que std :: runtime_error (y family) siguen siendo una excepción perfectamente aceptable para lanzar o derivar.
Martin York
5
@Emil: Derivar de otras excepciones estándar además std::exceptiones una excelente idea. Lo que no debes hacer es heredar de más de uno.
Mooing Duck
@MooingDuck: si deriva de más de un tipo de excepción estándar, terminará con std :: exception derivada (no virtualmente) varias veces. Consulte boost.org/doc/libs/release/libs/exception/doc/… .
Emil
1
@Emil: Eso se desprende de lo que dije.
Mooing Duck
13

Una vez participé en la limpieza de una gran base de código donde los autores anteriores habían arrojado ints, HRESULTS, std :: string, char *, clases aleatorias ... cosas diferentes en todas partes; solo nombre un tipo y probablemente fue arrojado a alguna parte. Y ninguna clase base común en absoluto. Créame, las cosas estaban mucho más ordenadas una vez que llegamos al punto en que todos los tipos lanzados tenían una base común que podíamos atrapar y sabíamos que nada iba a pasar. Así que hágase un favor a usted mismo (ya aquellos que tendrán que mantener su código en el futuro) y hágalo de esa manera desde el principio.

timday
fuente
12

Debería heredar de boost :: exception . Proporciona muchas más funciones y formas bien entendidas de transportar datos adicionales ... por supuesto, si no está usando Boost , ignore esta sugerencia.

James Schek
fuente
5
Sin embargo, tenga en cuenta que boost :: exception en sí mismo no se deriva de std :: exception. Incluso cuando se deriva de boost :: exception, también debe derivar de std :: exception (como una nota separada, siempre que se derive de tipos de excepción, debe usar la herencia virtual).
Emil
10

Sí, deberías derivar std::exception.

Otros han respondido que std::exceptiontiene el problema de que no puede pasarle un mensaje de texto, sin embargo, generalmente no es una buena idea intentar formatear un mensaje de usuario en el punto del lanzamiento. En su lugar, utilice el objeto de excepción para transportar toda la información relevante al sitio de captura, que luego puede formatear un mensaje fácil de usar.

Emil
fuente
6
+1, buen punto. Tanto desde la separación de preocupaciones como desde la perspectiva de i18n, definitivamente es mejor dejar que la capa de presentación construya el mensaje del usuario.
John M Gant
@JohnMGant y Emil: Interesante, ¿pueden señalar un ejemplo concreto sobre cómo se puede hacer esto? Entiendo que se puede derivar std::exceptiony llevar la información de la excepción. Pero, ¿quién será responsable de crear / formatear el mensaje de error? ¿La ::what()función o algo más?
alfC
1
Formatearía el mensaje en el sitio de captura, probablemente a nivel de aplicación (en lugar de biblioteca). Lo detecta por tipo, luego lo prueba en busca de información, luego localiza / formatea el mensaje de usuario apropiado.
Emil
9

La razón por la que puede querer heredar std::exceptiones porque le permite lanzar una excepción que se detecta según esa clase, es decir:

class myException : public std::exception { ... };
try {
    ...
    throw myException();
}
catch (std::exception &theException) {
    ...
}
SingleNegationElimination
fuente
6

Hay un problema con la herencia que debe conocer es la división de objetos. Cuando escribe throw e;una expresión de lanzamiento inicializa un objeto temporal, llamado objeto de excepción, el tipo del cual se determina eliminando cualquier calificador cv de nivel superior del tipo estático del operando de throw. Eso podría no ser lo que esperabas. Ejemplo de problema que puede encontrar aquí .

No es un argumento en contra de la herencia, es solo información "imprescindible".

Kirill V. Lyadvinsky
fuente
3
Creo que lo que se lleva es ese "tiro e"; es malo, y "tirar"; está bien.
James Schek
2
Sí, throw;está bien, pero no es obvio que debas escribir algo como esto.
Kirill V. Lyadvinsky
1
Es especialmente doloroso para los desarrolladores de Java cuando el relanzamiento se realiza usando "throw e;"
James Schek
Considere solo derivar de tipos base abstractos. La captura de un tipo de base abstracto por valor le dará un error (evitando cortar); capturar un tipo de base abstracto por referencia y luego intentar lanzar una copia le dará un error (nuevamente evitando cortar).
Emil
También puede resolver este problema mediante la adición de una función miembro, raise()como tal: virtual void raise() { throw *this; }. Solo recuerde anularlo en cada excepción derivada.
Justin Time - Reincorpora a Monica
5

Diferencia: std :: runtime_error vs std :: exception ()

Si debe heredar de él o no, depende de usted. Standard std::exceptiony sus descendientes estándar proponen una posible estructura de jerarquía de excepción (división en logic_errorsubjerarquía y runtime_errorsubjerarquía) y una posible interfaz de objeto de excepción. Si te gusta, úsalo. Si por alguna razón necesita algo diferente, defina su propio marco de excepción.

Hormiga
fuente
3

Dado que el lenguaje ya lanza std :: exception, debe detectarlo de todos modos para proporcionar un informe de error decente. También puede usar esa misma captura para todas las excepciones inesperadas propias. Además, casi cualquier biblioteca que arroje excepciones las derivaría de std :: exception.

En otras palabras, es

catch (...) {cout << "Unknown exception"; }

o

catch (const std::exception &e) { cout << "unexpected exception " << e.what();}

Y la segunda opción es definitivamente mejor.


fuente
3

Si todas sus posibles excepciones se derivan de std::exception, su bloque de captura puede simplemente catch(std::exception & e)y estar seguro de capturar todo.

Una vez que haya capturado la excepción, puede usar ese whatmétodo para obtener más información. C ++ no admite la escritura de pato, por lo que otra clase con un whatmétodo requeriría una captura diferente y un código diferente para usarlo.

Mark Ransom
fuente
7
no subclase std :: exception y luego capture (std :: exception e). Necesita capturar una referencia a std :: exception & (o un puntero) o de lo contrario está cortando los datos de la subclase.
jmucchiello
1
Eso fue un error tonto, ciertamente lo sé mejor. stackoverflow.com/questions/1095225/…
Mark Ransom
3

Si derivar de cualquier tipo de excepción estándar o no es la primera pregunta. Al hacerlo, se habilita un único controlador de excepciones para todas las excepciones estándar de la biblioteca y la suya propia, pero también fomenta esos controladores "catch-them-all". El problema es que solo se deben detectar las excepciones que se sabe cómo manejar. En main (), por ejemplo, detectar todas las excepciones std :: es probablemente algo bueno si la cadena what () se registrará como último recurso antes de salir. En otros lugares, sin embargo, es poco probable que sea una buena idea.

Una vez que haya decidido si derivar de un tipo de excepción estándar o no, la pregunta es cuál debería ser la base. Si su aplicación no necesita i18n, podría pensar que formatear un mensaje en el sitio de la llamada es tan bueno como guardar información y generar el mensaje en el sitio de la llamada. El problema es que es posible que el mensaje formateado no sea necesario. Es mejor usar un esquema de generación de mensajes perezoso, quizás con memoria preasignada. Luego, si el mensaje es necesario, se generará al acceder (y, posiblemente, se almacenará en caché en el objeto de excepción). Por lo tanto, si el mensaje se genera cuando se lanza, entonces se necesita un derivado std :: exception, como std :: runtime_error como clase base. Si el mensaje se genera de forma perezosa, entonces std :: exception es la base apropiada.

Robar
fuente
0

Otra razón para las excepciones de subclases es un mejor aspecto de diseño cuando se trabaja en grandes sistemas encapsulados. Puede reutilizarlo para cosas como mensajes de validación, consultas de usuarios, errores fatales del controlador, etc. En lugar de reescribir o reenganchar todos sus mensajes de validación, simplemente puede "capturarlos" en el archivo fuente principal, pero arrojar el error en cualquier parte de su conjunto completo de clases.

Por ejemplo, una excepción fatal terminará el programa, un error de validación solo borrará la pila y una consulta de usuario hará una pregunta al usuario final.

Hacerlo de esta manera también significa que puede reutilizar las mismas clases pero en diferentes interfaces. Por ejemplo, una aplicación de Windows puede usar un cuadro de mensaje, un servicio web mostrará html y el sistema de informes lo registrará y así sucesivamente.

James Burnby
fuente
0

Aunque esta pregunta es bastante antigua y ya se ha respondido mucho, solo quiero agregar una nota sobre cómo hacer un manejo adecuado de excepciones en C ++ 11, ya que continuamente me falta esto en las discusiones sobre excepciones:

Utilice std::nested_exceptionystd::throw_with_nested

Se describe en StackOverflow aquí y aquí , cómo puede obtener un seguimiento de sus excepciones dentro de su código sin necesidad de un depurador o un registro engorroso, simplemente escribiendo un controlador de excepciones adecuado que volverá a generar excepciones anidadas.

Dado que puede hacer esto con cualquier clase de excepción derivada, ¡puede agregar mucha información a dicho seguimiento! También puede echar un vistazo a mi MWE en GitHub , donde un backtrace se vería así:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

Ni siquiera necesita crear una subclase std::runtime_errorpara obtener mucha información cuando se lanza una excepción.

El único beneficio que veo en la subclasificación (en lugar de solo usar std::runtime_error) es que su controlador de excepciones puede detectar su excepción personalizada y hacer algo especial. Por ejemplo:

try
{
  // something that may throw
}
catch( const MyException & ex )
{
  // do something specialized with the
  // additional info inside MyException
}
catch( const std::exception & ex )
{
  std::cerr << ex.what() << std::endl;
}
catch( ... )
{
  std::cerr << "unknown exception!" << std::endl;
}
GPMueller
fuente