¿Se debe derivar / heredar de std :: exception?

15

Al diseñar mi primera biblioteca C ++ 'seria', me pregunto:

¿Es un buen estilo derivar las excepciones std::exceptiony sus descendientes?

Incluso después de leer

Todavía no estoy seguro. Porque, además de la práctica común (pero quizás no buena), supondría, como usuario de la biblioteca, que una función de biblioteca arrojaría std::exceptions solo cuando las funciones de biblioteca estándar fallaran en la implementación de la biblioteca, y no puede hacer nada al respecto. Pero aún así, al escribir el código de la aplicación, para mí es muy conveniente, y también en mi humilde opinión apuesto a tirar un std::runtime_error. Además, mis usuarios también pueden confiar en la interfaz mínima definida, como what()o códigos.

Y, por ejemplo, mi usuario proporciona argumentos defectuosos, ¿qué sería más conveniente que lanzar un std::invalid_argument, no? Entonces, combinado con el uso común de std :: excepción que veo en el código de otros: ¿Por qué no ir más allá y derivar de su clase de excepción personalizada (por ejemplo, lib_foo_exception) y también de std::exception.

Pensamientos?

Superlokkus
fuente
No estoy seguro de seguirlo. El hecho de que heredes de std::exceptionno significa que arrojes un std::exception. Además, std::runtime_errorhereda de std::exceptionen primer lugar, y el what()método proviene de std::exception, no std::runtime_error. Y definitivamente debe crear sus propias clases de excepción en lugar de lanzar excepciones genéricas como std::runtime_error.
Vincent Savard
33
La diferencia es que, cuando mi lib_foo_exceptionclase se deriva std::exception, el usuario de la biblioteca capturaría lib_foo_exceptionsimplemente capturando std::exception, además de cuando atrapa solo el de biblioteca. Por lo tanto, también podría preguntar si mi clase de raíz de excepción de biblioteca hereda de std :: exception .
Superlokkus
33
@LightnessRacesinOrbit Me refiero a "... además de", como "¿Cuántas maneras hay de atrapar lib_foo_exception?" Con heredar de std::exceptionusted puede hacerlo por catch(std::exception)OR por catch(lib_foo_exception). Sin derivar de std::exception, lo atraparías si y solo si , por catch(lib_foo_exception).
Superlokkus
2
@Superlokkus: Ignoramos catch(...). Está allí porque el lenguaje permite el caso que está considerando (y las bibliotecas que "se comportan mal"), pero esa no es la mejor práctica moderna.
Lightness compite con Monica el
1
Gran parte del diseño del manejo de excepciones en C ++ tiende a fomentar catchsitios más generales y más generales , y también transacciones más generales que modelan una operación de usuario final. Si lo compara con idiomas que no promueven la idea de la captura generalizada de std::exception&, por ejemplo, a menudo tienen mucho más código con try/catchbloques intermedios relacionados con errores muy específicos, lo que disminuye un poco la generalidad del manejo de excepciones a medida que comienza a ubicarse un énfasis mucho más fuerte en el manejo manual de errores, y también en todos los errores dispares que podrían ocurrir.

Respuestas:

29

Todas las excepciones deben heredar de std::exception.

Supongamos, por ejemplo, que necesito llamar ComplexOperationThatCouldFailABunchOfWays()y quiero manejar cualquier excepción que pueda arrojar. Si todo hereda de std::exception, esto es fácil. Solo necesito un solo catchbloque, y tengo una interfaz estándar ( what()) para obtener detalles.

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
}

Si las excepciones NO heredan std::exception, esto se vuelve mucho más feo:

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
} catch (Exception& e) {
    cerr << e.Message << endl;
} catch (framework_exception& e) {
    cerr << e.Details() << endl;
}

Con respecto a lanzar runtime_erroro no invalid_argumentcrear sus propias std::exceptionsubclases para lanzar: Mi regla general es introducir una nueva subclase cada vez que necesito manejar un tipo particular de error de manera diferente a otros errores (es decir, cada vez que necesito un catchbloque separado ).

  • Si introduzco una nueva subclase de excepción para cada tipo de error concebible, incluso si no necesito manejarlos por separado, eso agrega mucha proliferación de clases.
  • Si reutilizar subclases existentes a algo específico media (es decir, si un runtime_errortirado aquí significa algo diferente a un error de ejecución genérico), entonces corro el riesgo de entrar en conflicto con otros usos de la subclase existente.
  • Si no necesito manejar un error específicamente, y si el error que estoy lanzando coincide exactamente con uno de los errores de la biblioteca estándar existente (como invalid_argument), entonces reutilizo la clase existente. Simplemente no veo mucho beneficio al agregar una nueva clase en este caso. (Las Pautas principales de C ++ no están de acuerdo conmigo aquí; recomiendan usar siempre sus propias clases).

Las Pautas principales de C ++ tienen más discusión y ejemplos.

Josh Kelley
fuente
¡Todos esos símbolos! C ++ es raro.
SuperJedi224
2
@ SuperJedi224 y todas esas letras diferentes! El ingles es raro.
johannes
Este razonamiento no tiene sentido para mí. ¿No es esto para lo que catch (...)(con los puntos suspensivos literales) es?
Maxpm
1
@Maxpm catch (...)solo es útil si no necesita hacer nada con lo que se arroja. Si desea hacer algo, por ejemplo, mostrar o registrar el mensaje de error específico, como en mi ejemplo, entonces necesita saber qué es.
Josh Kelley
9

Como usuario de la biblioteca, supondría que una función de la biblioteca arrojaría excepciones std :: solo cuando las funciones estándar de la biblioteca fallaran en la implementación de la biblioteca, y no puede hacer nada al respecto

Esa es una suposición incorrecta.

Los tipos de excepción estándar se proporcionan para uso "más común". No están diseñados para ser utilizados únicamente por la biblioteca estándar.

Sí, haga que todo finalmente herede std::exception. A menudo, eso implicará heredar de std::runtime_erroro std::logic_error. Lo que sea apropiado para la clase de excepción que está implementando.

Por supuesto, esto es subjetivo: varias bibliotecas populares ignoran por completo los tipos de excepción estándar, presumiblemente para desacoplar las bibliotecas de la biblioteca estándar. ¡Personalmente creo que esto es extremadamente egoísta! Hace que la captura de excepciones sea mucho más difícil de acertar.

Hablando personalmente, a menudo solo tiro un std::runtime_errory termino. Pero eso es entrar en una discusión sobre qué tan granular hacer sus clases de excepción, que no es lo que está preguntando.

La ligereza corre con Mónica
fuente