Para mí, el manejo de excepciones puede ser excelente si todo se ajusta a RAII y está reservando excepciones para rutas verdaderamente excepcionales (por ejemplo, un archivo corrupto que se está leyendo) y nunca necesita cruzar los límites del módulo y todo su equipo está a bordo con ellos. ......
EH de costo cero
La mayoría de los compiladores en la actualidad implementan un manejo de excepciones de costo cero que puede hacerlo incluso más barato que la ramificación manual en condiciones de error en las rutas de ejecución regulares, aunque a cambio hace que las rutas excepcionales sean enormemente caras (también hay algo de código hinchado, aunque probablemente no más) que si manejas a fondo todos los errores posibles manualmente Sin embargo, el hecho de que el lanzamiento sea enormemente costoso no debería importar si las excepciones se usan de la manera en que están destinadas en C ++ (para circunstancias verdaderamente excepcionales que no deberían ocurrir en condiciones normales).
Inversión de efectos secundarios
Dicho esto, hacer que todo se ajuste a RAII es mucho más fácil decirlo que hacerlo. Es fácil para los recursos locales almacenados en una función envolverlos en objetos C ++ con destructores que se limpian solos, pero no es tan fácil escribir la lógica de deshacer / deshacer para cada efecto secundario que podría ocurrir en todo el software. Solo considere lo difícil que es escribir un contenedor como el std::vectorque es la secuencia de acceso aleatorio más simple para que sea perfectamente seguro para las excepciones. Ahora multiplique esa dificultad en todas las estructuras de datos de un software completo a gran escala.
Como ejemplo básico, considere una excepción encontrada en el proceso de insertar elementos en un gráfico de escena. Para recuperarse adecuadamente de esa excepción, es posible que deba deshacer esos cambios en el gráfico de escena y restaurar el sistema a un estado como si la operación nunca hubiera ocurrido. Eso significa eliminar automáticamente a los niños insertados en el gráfico de la escena al encontrar una excepción, tal vez desde un guardia de alcance.
Hacer esto a fondo en un software que causa muchos efectos secundarios y se ocupa de una gran cantidad de estado persistente es mucho más fácil decirlo que hacerlo. A menudo creo que la solución pragmática es no molestarse con el interés de enviar las cosas. Sin embargo, con el tipo correcto de base de código que tiene algún tipo de sistema central de deshacer y quizás estructuras de datos persistentes y el número mínimo de funciones que causan efectos secundarios, es posible que pueda lograr una seguridad de excepción en todos los ámbitos ... pero es mucho lo que necesita si todo lo que va a hacer es tratar de hacer que su código sea seguro en todas partes.
Hay algo prácticamente mal allí porque conceptualmente la reversión de los efectos secundarios es un problema difícil, pero se hace más difícil en C ++. En realidad, a veces es más fácil revertir los efectos secundarios en C en respuesta a un código de error que hacerlo en respuesta a una excepción en C ++. Una de las razones es porque cualquier punto dado de cualquier función podría potencialmente throwen C ++. Si está tratando de escribir un contenedor genérico que funcione con tipo T, ni siquiera puede anticipar dónde están esos puntos de salida, ya que cualquier cosa que implique Tpodría arrojarse. Incluso comparar uno Tcon otro por la igualdad podría arrojar. Su código tiene que manejar todas las posibilidades , y el número de posibilidades se multiplica exponencialmente en C ++, mientras que en C, son mucho menos numerosas.
Falta de estándares
Otro problema de manejo de excepciones es la falta de estándares centrales. Hay algunos que, con suerte, todos están de acuerdo en que los destructores nunca deberían arrojar, ya que los retrocesos nunca deberían fallar, pero eso solo cubre un caso patológico que generalmente bloquearía el software si viola la regla.
Debería haber algunas normas más sensatas como nunca lanzar desde un operador de comparación (todas las funciones que son lógicamente inmutables nunca deberían lanzar), nunca lanzar desde un ctor de movimiento, etc. Tales garantías facilitarían mucho más la escritura de códigos seguros para excepciones, pero no tenemos tales garantías. Tenemos que seguir la regla de que todo podría arrojar, si no ahora, posiblemente en el futuro.
Peor aún, las personas de diferentes orígenes lingüísticos ven las excepciones de manera muy diferente. ¡En Python, en realidad tienen esta filosofía de "salto antes de mirar" que implica desencadenar excepciones en las rutas de ejecución regulares! Cuando obtenga que dichos desarrolladores escriban código C ++, podría estar viendo excepciones lanzadas de izquierda a derecha para cosas que no son realmente excepcionales, y manejarlo todo puede ser una pesadilla si está tratando de escribir código genérico, por ejemplo
Manejo de errores
El manejo de errores tiene la desventaja de que debe verificar cada posible error que pueda ocurrir. Pero tiene una ventaja: a veces, en retrospectiva, puede verificar los errores un poco tarde, comoglGetError , para reducir significativamente los puntos de salida en una función y hacerlos explícitos. A veces puede seguir ejecutando el código un poco más antes de verificar, propagarse y recuperarse de un error, y a veces eso puede ser realmente más fácil que el manejo de excepciones (especialmente con la reversión de efectos secundarios). Pero es posible que ni siquiera te molestes tanto con los errores en un juego, tal vez solo apagues el juego con un mensaje si encuentras cosas como archivos corruptos, errores de falta de memoria, etc.
Cómo se relaciona con el desarrollo de bibliotecas utilizadas a través de múltiples proyectos.
Una parte desagradable de las excepciones en contextos DLL es que no puede lanzar excepciones de un módulo a otro de manera segura a menos que pueda garantizar que estén compiladas por el mismo compilador, use la misma configuración, etc. Entonces, si está escribiendo como una arquitectura de complemento destinado a personas de todo tipo de compiladores y posiblemente incluso de diferentes lenguajes como Lua, C, C #, Java, etc., a menudo las excepciones comienzan a convertirse en una molestia importante ya que hay que tragar todas las excepciones y traducirlas en error códigos de todos modos en todo el lugar.
¿Qué hace al usar bibliotecas de terceros?
Si son dylibs, entonces no pueden lanzar excepciones de manera segura por las razones mencionadas anteriormente. Tendrían que usar códigos de error, lo que también significa que, usando la biblioteca, tendría que verificar constantemente los códigos de error. Podrían envolver su dylib con una biblioteca de envoltura C ++ estáticamente vinculada que usted mismo construye, que traduce los códigos de error del dylib en excepciones lanzadas (básicamente arrojando desde su binario a su binario). En general, creo que la mayoría de las librerías de terceros ni siquiera deberían molestarse y limitarse a los códigos de error si usan dylibs / libs compartidas.
¿Cómo afecta a las pruebas unitarias, las pruebas de integración, etc.?
Por lo general, no encuentro equipos que prueben tanto las rutas de error / excepcionales de su código. Solía hacerlo y en realidad tenía un código que podía recuperarse correctamente bad_allocporque quería hacer mi código ultra robusto, pero realmente no ayuda cuando soy el único que lo hace en un equipo. Al final dejé de molestarme. Me imagino un software de misión crítica que equipos completos podrían verificar para asegurarse de que su código se recupere de manera sólida de excepciones y errores en todos los casos posibles, pero eso es mucho tiempo para invertir. El tiempo tiene sentido en el software de misión crítica, pero quizás no en un juego.
Amor / odio
Las excepciones también son mi tipo de característica de amor / odio de C ++, una de las características más impactantes del lenguaje que hace que RAII sea más un requisito que una conveniencia, ya que cambia la forma en que tiene que escribir el código al revés, por ejemplo , C para manejar el hecho de que cualquier línea de código dada podría ser un punto de salida implícito para una función. A veces casi deseo que el idioma no tenga excepciones. Pero hay momentos en que encuentro excepciones realmente útiles, pero son un factor demasiado dominante para mí si elijo escribir un fragmento de código en C o C ++.
Serían exponencialmente más útiles para mí si el comité estándar realmente se concentrara en establecer estándares ABI que permitieran que las excepciones se lanzaran de forma segura a través de los módulos, al menos desde el código C ++ a C ++. Sería increíble si pudieras lanzar idiomas de manera segura, aunque eso es una especie de sueño imposible. La otra cosa que haría las excepciones exponencialmente más útiles para mí es si las noexceptfunciones realmente causaron un error del compilador si intentaron invocar cualquier funcionalidad que pudiera generar en lugar de simplemente invocar std::terminateuna excepción. Eso es casi inútil (también podría llamarse así en icantexceptlugar de noexcept). Sería mucho más fácil asegurarse de que todos los destructores estén a salvo de excepciones, por ejemplo, sinoexcepten realidad causó un error de compilación si el destructor hizo algo que podría arrojar. Si eso es demasiado costoso para determinar a fondo en tiempo de compilación, simplemente haga que lasnoexceptlas funciones (que todos los destructores deberían ser implícitamente) solo pueden llamar a noexceptfunciones / operadores, y convertirlo en un error del compilador desde cualquiera de ellos.