c++ - son - try catch++
¿Cuándo deberías crear tu propio tipo de excepción? (6)
Crea tus propios tipos de excepción cuando:
- Es posible que desee diferenciarlos al manejar. Si son tipos diferentes, tienes la opción de escribir diferentes cláusulas
catch
. Todavía deben tener una base común para que pueda tener un manejo común cuando sea apropiado - desea agregar algunos datos estructurados específicos que realmente puede utilizar en el controlador
Tener list
separadas y excepciones vector
no parece valer la pena, a menos que haya algo distintivo o vectoroso en ellas. ¿Realmente va a tener un manejo de captura diferente dependiendo de qué tipo de contenedor tuvo un error?
A la inversa, podría tener sentido tener tipos de excepción separados para las cosas que podrían recuperarse en el tiempo de ejecución, en comparación con las cosas que se retrotraen pero que se podrían volver a intentar, en lugar de las que son definitivamente fatales o indican un error.
Estoy trabajando en un proyecto en el que estamos reestructurando el código C antiguo en C ++ nuevo, donde para el manejo de errores estamos usando excepciones.
Estamos creando diferentes tipos de excepciones para diferentes módulos.
No veo que valga la pena, pero no tengo ningún argumento válido para demostrar mi punto. Entonces, si hubiéramos escrito la biblioteca estándar, veríamos vector_exception, list_exception y así sucesivamente.
Mientras lo pensaba, me topé con esta pregunta:
¿Cuándo debería crear su propio tipo de excepción y cuándo debería atenerse a las excepciones ya creadas en la biblioteca estándar?
Además, ¿cuáles podrían ser los problemas a los que nos enfrentaremos en el futuro cercano si seguimos el enfoque anterior?
Creo que además de las razones en otras respuestas podría ser la legibilidad del código, porque los programadores pasan mucho tiempo apoyándolo. Considere dos piezas de código (asumiendo que arrojan un error de "marco vacío"):
void MyClass::function() noexcept(false) {
// ...
if (errorCondition) {
throw std::exception("Error: empty frame");
}
}
void MyClass::function() noexcept(false) {
// ...
if (errorCondition) {
throw EmptyFrame();
}
}
En el segundo caso, creo que es más legible, y el mensaje para el usuario (que imprimió con la función what) está oculto dentro de esta clase de excepción personalizada.
El problema que veo con cada elemento de la STL que dispara una excepción de memoria distinta es que algunas partes de la STL se basan en otras, por lo que la cola tendría que capturar y traducir las excepciones de la lista, o los clientes de la cola tendrían que saber para manejar las excepciones de la lista .
Puedo ver algo de lógica al separar las excepciones de diferentes módulos, pero también puedo ver la lógica de tener una clase base de excepción para todo el proyecto. También puedo ver la lógica de tener clases de tipo de excepción.
La alianza profana sería construir una jerarquía de excepciones:
std::exception
EXProjectBase
EXModuleBase
EXModule1
EXModule2
EXClassBase
EXClassMemory
EXClassBadArg
EXClassDisconnect
EXClassTooSoon
EXClassTooLate
Luego insista en que cada excepción real disparada se derive de AMBOS un módulo y una clasificación. Luego, puede capturar lo que tenga sentido para el receptor, como una desconexión, en lugar de tener que capturar por separado HighLevelDisconnect y LowLevelDisconnect.
Por otro lado, también es completamente justo sugerir que la interfaz de alto nivel debería haber resuelto completamente los fallos de bajo nivel, y que nunca deberían ser vistos por el cliente de API de alto nivel. Aquí es donde la captura por módulo sería una característica útil de último recurso.
Me gustaría preguntar, de la sección try...catch
, "¿este código sabe cómo recuperarse del error, o no?
El código que utiliza try...catch
anticipa que podría ocurrir una excepción. Las razones por las que escribiría try...catch
en absoluto en lugar de dejar pasar la excepción son:
- Agregue algún código para hacer que la excepción de la función principal sea segura Mucho de esto debe hacerse silenciosamente con RAII en los destructores, pero a veces (por ejemplo, una operación de movimiento) necesita más trabajo para revertir un trabajo parcialmente completado, depende del nivel de seguridad de excepción que desee.
- Emita o agregue información de depuración y vuelva a emitir la excepción.
- Intenta recuperarte del error.
Para los casos 1 y 2, probablemente no tenga que preocuparse por los tipos de excepción de usuario, se verán así
try {
....
} catch (const std::exception & e) {
// print or tidy up or whatever
throw;
} catch (...) {
// print or tidy up or whatever
// also complain that std::exception should have been thrown
throw;
}
Esto probablemente será del 99% de los casos del mundo real, en mi experiencia.
A veces querrá recuperarse del error. Tal vez la función principal sepa que una biblioteca fallará en ciertas condiciones que pueden solucionarse dinámicamente , o existe una estrategia para volver a intentar un trabajo utilizando diferentes mecanismos.
A veces, el bloque catch
se escribirá específicamente porque el código de nivel inferior ha sido diseñado para lanzar excepciones como parte de su flujo normal . (A menudo hago esto cuando leo datos externos que no son de confianza, donde muchas cosas pueden salir mal, como era de esperar ).
En estos casos más raros, los tipos definidos por el usuario tienen sentido.
Usar la misma excepción en todas partes es fácil. Especialmente cuando se trata de atrapar esa excepción. Desafortunadamente, se abre la puerta para el manejo de excepciones Pokemon. Trae el riesgo de atrapar excepciones que no esperas.
El uso de una excepción dedicada para todos los módulos diferentes agrega varias ventajas:
- Un mensaje personalizado para cada caso, lo que lo hace más útil para los informes.
- La captura puede capturar solo las excepciones previstas, se bloquea más fácilmente en casos inesperados (sí, eso es una ventaja sobre el manejo incorrecto)
- En caso de fallo, IDE puede mostrar el tipo de excepción, lo que ayuda aún más con la depuración
Veo dos posibles razones.
1) Si desea almacenar en la clase de excepción alguna información personalizada sobre la excepción, entonces necesita una excepción con miembros de datos adicionales. A menudo heredarás de una de las clases de excepción estándar.
2) Si quieres diferenciar entre excepciones. Por ejemplo, suponga que tiene dos situaciones distintas de invalid argument
y en su bloque catch
desea poder diferenciar entre los dos. Puedes crear dos clases secundarias de std::invalid_argument