En primer lugar, como otros han dicho, las cosas no son que claro corte en C ++, en mi humilde opinión sobre todo debido a los requisitos y las restricciones son algo más variado en C ++ que otros lenguajes, esp. C # y Java, que tienen problemas de excepción "similares".
Expondré en el ejemplo std :: stof:
pasar una cadena vacía a std :: stof (arrojará inválido_argumento) no es un error de programación
El contrato básico , tal como lo veo, de esta función es que intenta convertir su argumento en flotante, y cualquier falla al hacerlo se informa por una excepción. Ambas posibles excepciones se derivan, logic_error
pero no en el sentido de error del programador, sino en el sentido de que "la entrada no se puede convertir en flotante".
Aquí, uno puede decir que logic_error
se usa a para indicar que, dada esa entrada (tiempo de ejecución), siempre es un error intentar convertirlo, pero es el trabajo de la función determinar eso y decírselo (a través de una excepción).
Nota al margen: en esa vista, a runtime_error
podría verse como algo que, dada la misma entrada a una función, teóricamente podría tener éxito para diferentes ejecuciones. (por ejemplo, una operación de archivo, acceso a base de datos, etc.)
Nota adicional: La biblioteca de expresiones regulares de C ++ eligió derivar su error, runtime_error
aunque hay casos en los que podría clasificarse de la misma manera que aquí (patrón de expresiones regulares no válido).
Esto solo muestra, en mi humilde opinión, que la agrupación logic_
o el runtime_
error es bastante confuso en C ++ y realmente no ayuda mucho en el caso general (*): si necesita manejar errores específicos, probablemente necesite atrapar más bajo que los dos.
(*): Eso no quiere decir que una sola pieza de código no debe ser consistente, pero si usted lanza runtime_
o logic_
o custom_
tantos no es realmente tan importante, creo.
Para comentar sobre ambos stof
y bitset
:
Ambas funciones toman cadenas como argumento, y en ambos casos es:
- no es trivial verificar si la persona que llama es válida (por ejemplo, en el peor de los casos, tendría que replicar la lógica de la función; en el caso de bitset, no está claro de inmediato si la cadena vacía es válida, así que deje que el ctor decida)
- Ya es responsabilidad de la función "analizar" la cadena, por lo que ya tiene que validar la cadena, por lo que tiene sentido que informe un error para "usar" la cadena de manera uniforme (y en ambos casos esto es una excepción) .
La regla que aparece con frecuencia con excepciones es "usar solo excepciones en circunstancias excepcionales". Pero, ¿cómo se supone que una función de biblioteca sabe qué circunstancias son excepcionales?
Esta declaración tiene, en mi humilde opinión, dos raíces:
Rendimiento : si se llama a una función en una ruta crítica, y el caso "excepcional" no es excepcional, es decir, una cantidad significativa de pases implicará lanzar una excepción, entonces pagar cada vez por la maquinaria de desenrollado de excepción no tiene sentido , y puede ser demasiado lento.
Localidad del manejo de errores : si se invoca una función y la excepción se captura y procesa de inmediato, entonces tiene poco sentido lanzar una excepción, ya que el manejo de errores será más detallado con el catch
que con un if
.
Ejemplo:
float readOrDefault;
try {
readOrDefault = stof(...);
} catch(std::exception&) {
// discard execption, just use default value
readOrDefault = 3.14f; // 3.14 is the default value if cannot be read
}
Aquí es donde entran en juego funciones como TryParse
vs Parse
.: una versión para cuando el código local espera que la cadena analizada sea válida, una versión cuando el código local supone que realmente se espera (es decir, no excepcional) que el análisis falle.
De hecho, stof
es solo (definido como) un contenedor strtof
, así que si no quieres excepciones, usa esa.
Entonces, ¿cómo se supone que debo decidir si debo usar excepciones o no para una función en particular?
En mi humilde opinión, tienes dos casos:
Función similar a "Biblioteca" (reutilizada a menudo en diferentes contextos): Básicamente no puede decidir. Posiblemente proporcione ambas versiones, tal vez una que informe un error y una envoltura que convierta el error devuelto en una excepción.
La función "Aplicación" (específica para un blob de código de aplicación, puede reutilizarse en parte, pero está restringida por el estilo de manejo de errores de las aplicaciones, etc.): Aquí, a menudo debería ser bastante claro. Si la (s) ruta (s) de código que llama a las funciones manejan las excepciones de una manera sensata y útil, use las excepciones para informar cualquier error (pero vea a continuación) . Si el código de la aplicación se lee y escribe más fácilmente para un estilo de retorno de error, utilícelo por todos los medios.
Por supuesto, habrá lugares intermedios, solo use lo que necesita y recuerde YAGNI.
Por último, creo que debería volver a la declaración de preguntas frecuentes,
No utilice throw para indicar un error de codificación en el uso de una función. Utilice el aserción u otro mecanismo para enviar el proceso a un depurador o para bloquear el proceso ...
Me suscribo a esto para todos los errores que son una clara indicación de que algo está muy mal o que el código de llamada claramente no sabía lo que estaba haciendo.
Pero cuando esto es apropiado, a menudo es altamente específico de la aplicación, por lo tanto, consulte el dominio de biblioteca anterior vs.
Esto recae en la pregunta sobre si y cómo validar las condiciones previas de llamada , pero no voy a entrar en eso, responder ya demasiado tiempo :-)