¿Debo heredar de std :: exception?

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

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

Entonces, ¿cuáles son los beneficios, si los hubiera, de usar std::exception como clase base para mi clase de excepción específica de la aplicación? ¿Hay alguna buena razón para no heredar de std::exception ?

El principal beneficio es que el código que usa sus clases no tiene que saber el tipo exacto de lo que throw , sino que simplemente puede catch la std::exception .

Editar: como señalaron Martin y otros, en realidad desea derivar de una de las subclases de std::exception declaradas en el encabezado .

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

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

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

Si busca comodidad, puede heredar de std::runtime_error que proporciona std::string constructor.

Una vez participé en la limpieza de una gran base de código donde los autores anteriores habían lanzado ints, HRESULTS, std :: string, char *, clases aleatorias … cosas diferentes en todas partes; simplemente nombra un tipo y probablemente fue arrojado a algún lugar. Y no hay una clase base común en absoluto. Créanme, las cosas estaban mucho más ordenadas una vez que llegamos al punto de que todos los tipos arrojados tenían una base común que podíamos atrapar y saber que nada iba a pasar. Por lo tanto, hágase usted mismo (y los que deberán mantener su código en el futuro) un favor y hágalo de esa manera desde el principio.

Deberías heredar de boost :: exception . Proporciona muchas más funciones y formas bien entendidas de llevar datos adicionales … por supuesto, si no estás usando Boost , entonces ignora esta sugerencia.

La razón por la que es posible que desee heredar de std::exception es porque le permite lanzar una excepción que está atrapada según esa clase, es decir:

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

Sí, debería derivar de std::exception .

Otros han respondido que std::exception tiene 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 momento 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.

Hay un problema con la herencia del que debes saber sobre el corte de objetos. Cuando escribes throw e; una expresión de lanzamiento inicializa un objeto temporal, llamado objeto de excepción, cuyo tipo se determina eliminando cualquier cualificador cv de nivel superior del tipo estático del operando de throw . Eso podría no ser lo que estás esperando. Ejemplo de problema que podrías encontrar aquí .

No es un argumento en contra de la herencia, es solo información ‘debe saber’.

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

Si usted debe heredar de él o no depende de usted. Standard std::exception y sus descendientes estándar proponen una posible estructura de jerarquía de excepciones (división en sub jerarquía de lógica_error y subárquía de runtime_error ) 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.

Dado que el lenguaje ya arroja std :: exception, necesita detectarlo de todos modos para proporcionar informes de errores decentes. También puede usar esa misma captura para todas sus excepciones inesperadas. Además, casi cualquier biblioteca que arroje excepciones los derivaría de std :: exception.

En otras palabras, es cualquiera

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

o

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

Y la segunda opción es definitivamente mejor.

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

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

Si derivar de cualquier tipo de excepción estándar o no es la primera pregunta. Al hacerlo, habilita un manejador de excepciones único para todas las excepciones de biblioteca estándar y las suyas propias, pero también alienta a tales manejadores catch-them-all. El problema es que uno solo debe atrapar excepciones que uno sabe cómo manejar. En main (), por ejemplo, capturar todas las excepciones std :: es probable que sea bueno si la cadena what () se registra como último recurso antes de salir. En otro lugar, sin embargo, es poco probable que sea una buena idea.

Una vez que haya decidido si se deriva de un tipo de excepción estándar o no, entonces 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 llamadas. El problema es que el mensaje formateado puede no ser necesario. Es mejor utilizar un esquema de generación de mensajes perezosos, tal vez con memoria preasignada. Entonces, si el mensaje es necesario, se generará en el acceso (y, posiblemente, en caché en el objeto de excepción). Por lo tanto, si el mensaje se genera cuando se lanza, entonces se necesita una derivada std :: exception, como std :: runtime_error como clase base. Si el mensaje se genera de forma perezosa, std :: exception es la base adecuada.

Otra razón para las excepciones de subclase es un aspecto de diseño mejor cuando se trabaja en sistemas encapsulados de gran tamaño. Puede reutilizarlo para cosas tales como mensajes de validación, consultas de usuarios, errores fatales del controlador, etc. En lugar de reescribir o reacoplar todos sus mensajes de validación, simplemente puede “atraparlo” en el archivo fuente principal, pero arrojar el error en cualquier parte de su conjunto de clases.

Por ejemplo, una excepción fatal dará por terminado el progtwig, un error de validación solo borrará la stack y una consulta del usuario le 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 el cuadro de mensaje, un servicio web mostrará html y el sistema de informes lo registrará, y así sucesivamente.

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

Use std::nested_exception y std::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.

¡Ya que puede hacer esto con cualquier clase de excepción derivada, puede agregar mucha información a tal retroceso! También puede echar un vistazo a mi MWE en GitHub , donde una traza inversa 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 subclasificar std::runtime_error para obtener mucha información cuando se lanza una excepción.

El único beneficio que veo en la creación de subclases (en lugar de solo usar std::runtime_error ) es que su manejador 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; }